NextJS throws error on Firebase Functions Deploy - reactjs

TLDR; I'm following an example from NextJS using Firebase, and with minimal change I can't push to Firebase.
I am following the NextJS with-firebase-hosting-and-typescript example, and in accordance with the help from #8893.
I changed the deploy script in package.json to cross-env NODE_ENV=production firebase deploy.
I also changed the conf value in functions/index.ts to
conf: {
distDir: `${path.relative(process.cwd(), __dirname)}/../functions/next`
}
When I go to deploy the app to firebase I now receive an error
Deployment error.
Error setting up the execution environment for your function. Please try deploying again after a few minutes.
I did some debugging and if I comment out the line
const app = next({ dev, conf: { distDir: `${path.relative(process.cwd(), __dirname)}/../functions/next` }
})
in functions/index.ts, then the functions will deploy just fine. So, the issue seems to be with next()
Here is code of the functions/index.ts, this throws the error.
import * as functions from 'firebase-functions'
import next from 'next'
import * as path from 'path'
const appSetup = {
dev: process.env.NODE_ENV !== 'production',
conf: { distDir: `${path.relative(process.cwd(), __dirname)}/../functions/next` }
}
console.log("appSetup: ", appSetup)
const app = next(appSetup)
// const handle = app.getRequestHandler()
export const nextApp = functions.https.onRequest(async(req, res) => {
// return app.prepare().then(() => handle(req, res))
return res.send({ status: "Hello from Firebase!, nextApp" })
})
Here is code of the functions/index.ts, this DOES NOT throw an error
import * as functions from 'firebase-functions'
import next from 'next'
import * as path from 'path'
const appSetup = {
dev: process.env.NODE_ENV !== 'production',
conf: { distDir: `${path.relative(process.cwd(), __dirname)}/../functions/next` }
}
console.log("appSetup: ", appSetup)
// const app = next(appSetup)
// const handle = app.getRequestHandler()
export const nextApp = functions.https.onRequest(async(req, res) => {
// return app.prepare().then(() => handle(req, res))
return res.send({ status: "Hello from Firebase!, nextApp" })
})
in package.json
"firebase-admin": "^8.10.0",
"firebase-functions": "^3.6.0",
"next": "^9.3.5",
"react": "16.13.1",
"react-dom": "16.13.1"

For anyone struggling with the same issue. The fix was the line in functions/index.ts
I needed to replace
conf: { distDir: `${path.relative(process.cwd(), __dirname)}/../functions/next` }
to
conf: { distDir: `${path.relative(process.cwd(), __dirname)}/next` }

Related

FetchError: invalid json response body while deploying the Nextjs application on a server [duplicate]

I don't understand these errors when I export as production npm run build , but when I test npm run dev it works just fine. I use getStaticProps and getStaticPath fetch from an API route.
First when I npm run build
FetchError: invalid json response body at https://main-website-next.vercel.app/api/products reason: Unexpected token T in JSON at position
0
at D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:272:32
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async getStaticPaths (D:\zummon\Main Website\main-website-next\.next\server\pages\product\[slug].js:1324:18)
at async buildStaticPaths (D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:16:80)
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:26:612
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\tracer.js:1:1441 {
type: 'invalid-json'
}
\pages\product\[slug]
import { assetPrefix } from '../../next.config'
export default function Page(){...}
export const getStaticProps = async ({ params: { slug }, locale }) => {
const res = await fetch(`${assetPrefix}/api/products/${slug}`)
const result = await res.json()
const data = result.filter(item => item.locale === locale)[0]
const { title, keywords, description } = data
return {
props: {
data,
description,
keywords,
title
}
}
}
export const getStaticPaths = async () => {
const res = await fetch(`${assetPrefix}/api/products`)
const result = await res.json()
const paths = result.map(({ slug, locale }) => ({ params: { slug: slug }, locale }))
return {
fallback: true,
paths,
}
}
next.config.js
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
assetPrefix: isProd ? 'https://main-website-next.vercel.app' : 'http://localhost:3000',
i18n: {
localeDetection: false,
locales: ['en', 'th'],
defaultLocale: 'en',
}
}
API routes
// pages/api/products/index.js
import data from '../../../data/products'
export default (req, res) => {
res.status(200).json(data)
}
// pages/api/products/[slug].js
import db from '../../../data/products'
export default ({ query: { slug } }, res) => {
const data = db.filter(item => item.slug === slug)
if (data.length > 0) {
res.status(200).json(data)
} else {
res.status(404).json({ message: `${slug} not found` })
}
}
// ../../../data/products (data source)
module.exports = [
{ locale: "en", slug: "google-sheets-combine-your-cashflow",
title: "Combine your cashflow",
keywords: ["Google Sheets","accounting"],
description: "...",
},
...
]
Second when I remove the production domain, I run npm run build but still get the error like
TypeError: Only absolute URLs are supported
at getNodeRequestOptions (D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1305:9)
at D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1410:19
at new Promise (<anonymous>)
at fetch (D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1407:9)
at getStaticPaths (D:\zummon\Main Website\main-website-next\.next\server\pages\[slug].js:938:21)
at buildStaticPaths (D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:16:86)
at D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:26:618
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\tracer.js:1:1441 {
type: 'TypeError'
}
My next.config.js after remove
const isProd = process.env.NODE_ENV === 'production'
module.exports = { //remove
assetPrefix: isProd ? '' : 'http://localhost:3000',
i18n: {
localeDetection: false,
locales: ['en', 'th'],
defaultLocale: 'en',
}
}
My package.json when I npm run build script
{
"name": "main-website-next",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "next start"
},
"dependencies": {
"next": "10.0.6",
"react": "17.0.1",
"react-dom": "17.0.1"
}
}
You should not call an internal API route inside getStaticProps. Instead, you can safely use your API logic directly in getStaticProps/getStaticPaths. These only happen server-side so you can write server-side code directly.
As getStaticProps runs only on the server-side, it will never run on
the client-side. It won’t even be included in the JS bundle for the
browser, so you can write direct database queries without them being
sent to browsers.
This means that instead of fetching an API route from
getStaticProps (that itself fetches data from an external source),
you can write the server-side code directly in getStaticProps.
Furthermore, your API routes are not available during build-time, as the server has not been started at that point.
Here's a small refactor of your code to address the issue.
// /pages/product/[slug]
import db from '../../../data/products'
// Remaining code..
export const getStaticProps = async ({ params: { slug }, locale }) => {
const result = db.filter(item => item.slug === slug)
const data = result.filter(item => item.locale === locale)[0]
const { title, keywords, description } = data
return {
props: {
data,
description,
keywords,
title
}
}
}
export const getStaticPaths = async () => {
const paths = db.map(({ slug, locale }) => ({ params: { slug: slug }, locale }))
return {
fallback: true,
paths,
}
}

SvelteKit's how to omit host address in fetch url under proxy

I've configured proxy, so http://localhost:3000/api/articles goes to http://127.0.0.1:8000/articles
svelte.config.js:
const config = {
kit: {
target: '#svelte',
vite: {
server: {
proxy: {
'/api': {
target: 'http://127.0.0.1:8000',
rewrite: (path) => path.replace(/^\/api/, ''),
changeOrigin: true,
}
}
}
}
}
};
And requests like this works just fine:
<script context="module">
export const load = async ({ fetch }) => {
const res = await fetch('http://localhost:3000/api/articles');
...
}
</script>
But they do not work if host is omitted:
<script context="module">
export const load = async ({ fetch }) => {
const res = await fetch('/api/articles');
...
}
</script>
res contains 404 error
Playing with https://kit.svelte.dev/docs#configuration-host did not help
So, is it possible to omit host in load's fetch under proxy?
Thank you for sharing your vite configuration. I'm using #sveltejs/kit#1.0.0-next.522, which runs in dev mode on Port 5173 and i am runnig an http-server on port 8080, which simply responses »Good morning, world!«.
// vite.config.ts
import { sveltekit } from '#sveltejs/kit/vite';
import type { UserConfig } from 'vite';
const config: UserConfig = {
plugins: [sveltekit()],
server: {
proxy: {
'/api': {
target: 'http://[::1]:8080',
rewrite: (path) => path.replace(/^\/api/, ''),
changeOrigin: true
}
}
}
};
export default config;
// src/routes/hello/+page.svelte
<script lang="ts">
const hi = fetch('/api').then((x) => x.text());
</script>
{#await hi}
<p>Loading ...</p>
{:then data}
<p>Greeting: {data}</p>
{:catch error}
<p>Error {error.message}</p>
{/await}
On http://127.0.0.1:5173/hello my browser renders first Loading ... and then settles to Greeting: Good morning, world!.

Proxying requests in a create-react-app application

I have been looking all over to try and figure out why this doesn't work. I have two applications, a client and a server. I'd like to proxy the client requests to the server. I have a route called /api/repositories. I can't have the proxy in the package.json, because it needs to work in production, also.
It is a Create React App project. Here are the important files.
setupProxy.js
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = (app) => {
app.use(
createProxyMiddleware("/api", {
target: "http://my.server.com",
changeOrigin: true,
onProxyReq: (proxyReq) => {
console.log("logged");
if (proxyReq.getHeader("origin")) {
proxyReq.setHeader("origin", "http://my.server.com");
}
},
})
);
};
And I use it in a functional React component called Searchbar, as such:
Searchbar.js
import axios from "axios"
async function getRepos() {
const response = await axios({
method: "GET",
url: "/api/repositories",
});
return response.data;
}
function Searchbar() {
const [repos, setRepos] = useState([]);
// Get the repos on load
useEffect(async () => {
setRepos(await getRepos());
}, []);
return (
<div>
{repos.map((repo) => <p>{repo}<p>)}
</div>
);
}
However, when I run npm run start and run the development server, all of my API requests are going to http://localhost:3000/api/repositories, which obviously returns a 404 error. What am I doing wrong?

Access masked URLs in next.js

I have a page called "Channels", the final url should look like messages/:channelName, the following Link partially solves the problem:
<Link key={ name }
prefetch href={ `/channel?channel=${name}` }
as={`/messages/${name}`} >
Problem is, if i directly type this masked URL on the browser i get a 404, i can't refresh the page nor use the return button on the browser. I know this can be solved by creating these routes in the server and referring to the correct pages, but i'm trying to do this using only Next.js, is it possible?
Ofcourse you can do that by only use Next.js.
package.json
{
"name": "custom-server",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
},
"dependencies": {
"next": "latest",
"react": "^16.7.0",
"react-dom": "^16.7.0"
}
}
server.js
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/messages/:name') {
app.render(req, res, '/channel', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(port, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
channel.js
import React from "react";
export default (prop) => {
return <div>channel {prop.url.query.channel}</div>;
}

Unable run isomorphic single page app on firebase with ES6

I have an isomorphic react app. It has a server.js file inside app/src/ directory.
Server.js
import path from 'path';
import express from 'express';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import expressJwt, { UnauthorizedError as Jwt401Error } from 'express-jwt';
import nodeFetch from 'node-fetch';
import React from 'react';
import ReactDOM from 'react-dom/server';
import PrettyError from 'pretty-error';
import App from './components/App';
import Html from './components/Html';
import { ErrorPageWithoutStyle } from './routes/error/ErrorPage';
import errorPageStyle from './routes/error/ErrorPage.css';
import createFetch from './createFetch';
import router from './router';
import assets from './assets.json'; // eslint-disable-line import/no-unresolved
import configureStore from './store/configureStore';
import { setRuntimeVariable } from './actions/runtime';
import config from './config';
const app = express();
//
// Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the
// user agent is not known.
// -----------------------------------------------------------------------------
global.navigator = global.navigator || {};
global.navigator.userAgent = global.navigator.userAgent || 'all';
//
// Register Node.js middleware
// -----------------------------------------------------------------------------
app.use(express.static(path.resolve(__dirname, 'public')));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//
// Authentication
// -----------------------------------------------------------------------------
app.use(
expressJwt({
secret: config.auth.jwt.secret,
credentialsRequired: false,
getToken: req => req.cookies.id_token,
}),
);
// Error handler for express-jwt
app.use((err, req, res, next) => {
// eslint-disable-line no-unused-vars
if (err instanceof Jwt401Error) {
console.error('[express-jwt-error]', req.cookies.id_token);
// `clearCookie`, otherwise user can't use web-app until cookie expires
res.clearCookie('id_token');
}
next(err);
});
if (__DEV__) {
app.enable('trust proxy');
}
//
// Register server-side rendering middleware
// -----------------------------------------------------------------------------
app.get('*', async (req, res, next) => {
try {
const css = new Set();
// Universal HTTP client
const fetch = createFetch(nodeFetch, {
baseUrl: config.api.serverUrl,
cookie: req.headers.cookie,
});
const initialState = {
user: req.user || null,
};
const store = configureStore(initialState, {
fetch,
// I should not use `history` on server.. but how I do redirection? follow universal-router
});
store.dispatch(
setRuntimeVariable({
name: 'initialNow',
value: Date.now(),
}),
);
// Global (context) variables that can be easily accessed from any React component
// https://facebook.github.io/react/docs/context.html
const context = {
// Enables critical path CSS rendering
// https://github.com/kriasoft/isomorphic-style-loader
insertCss: (...styles) => {
// eslint-disable-next-line no-underscore-dangle
styles.forEach(style => css.add(style._getCss()));
},
fetch,
// You can access redux through react-redux connect
store,
storeSubscription: null,
};
const route = await router.resolve({
...context,
pathname: req.path,
query: req.query,
});
if (route.redirect) {
res.redirect(route.status || 302, route.redirect);
return;
}
const data = { ...route };
data.children = ReactDOM.renderToString(
<App context={context} store={store}>
{route.component}
</App>,
);
data.styles = [{ id: 'css', cssText: [...css].join('') }];
data.scripts = [assets.vendor.js];
if (route.chunks) {
data.scripts.push(...route.chunks.map(chunk => assets[chunk].js));
}
data.scripts.push(assets.client.js);
data.app = {
apiUrl: config.api.clientUrl,
state: context.store.getState(),
};
const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);
res.status(route.status || 200);
res.send(`<!doctype html>${html}`);
} catch (err) {
next(err);
}
});
//
// Error handling
// -----------------------------------------------------------------------------
const pe = new PrettyError();
pe.skipNodeFiles();
pe.skipPackage('express');
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
console.error(pe.render(err));
const html = ReactDOM.renderToStaticMarkup(
<Html
title="Internal Server Error"
description={err.message}
styles={[{ id: 'css', cssText: errorPageStyle._getCss() }]} // eslint-disable-line no-underscore-dangle
>
{ReactDOM.renderToString(<ErrorPageWithoutStyle error={err} />)}
</Html>,
);
res.status(err.status || 500);
res.send(`<!doctype html>${html}`);
});
//
// Launch the server
// -----------------------------------------------------------------------------
if (!module.hot) {
app.listen(config.port, () => {
console.info(`The server is running at http://localhost:${config.port}/`);
});
}
//
// Hot Module Replacement
// -----------------------------------------------------------------------------
if (module.hot) {
app.hot = module.hot;
module.hot.accept('./router');
}
export default app;
I want to deploy my app using firebase. For that I have setup firebase.json like so -
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "build",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"function": "app"
}
]
}
}
where app is a firebase function defined inside app/functions/ i.e. src and functions have same parent directory.
Functions directory has node_modules, properly configured and its working when tested for other functions.
Problem -
My index.js file inside app/functions is like so -
import app from '../src/server';
import functions from 'firebase-functions';
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
exports.app = functions.https.onRequest(app);
Firebase is complaining that it doesnt support ES6. How do I get this function to work with ES6? I cannot simply change functions/index.js file to ES5 and hope things will work as when requires are resolved, they'd expect everything inside to be ES5 as well which is not the case as my entire codebase is in ES6.
I found the solution to this problem.
Make a directory in functions/ say 'build'.
Build your project
Copy all the contents of your ./dist or ./build (wherever your build files are present) to functions/build
Modify functions/index.js as follows -
const app = require('./build/bundle.js').default;
const functions = require('firebase-functions');
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
exports.app = functions.https.onRequest(app);
Works like a charm

Resources