API request not being made in production build
Unanswered
Giant Angora posted this in #help-forum
Giant AngoraOP
I have a basic Next.js a single page on the root which in order to render some images needs to make an API request to retrieve the image URLs.
This works during development but when I build the app and run it in an environment where IMAGE_API_URL is set correctly.
The result is always No images found!
What gives?
I've even attached a Node.js debugger to the running server process and I can see with the transpiled JS code it never gets to
This works during development but when I build the app and run it in an environment where IMAGE_API_URL is set correctly.
The result is always No images found!
What gives?
I've even attached a Node.js debugger to the running server process and I can see with the transpiled JS code it never gets to
await fetch(...) even though process.env.IMAGE_API_URL is set and available.import Image from 'next/image';
async function getImages() {
const IMAGE_API_URL = process.env.IMAGE_API_URL;
// This was added because otherwise the production build would fail due to trying to make a request to this API which is not available at build time.
if (!IMAGE_API_URL) {
return [];
}
const res = await fetch(`${IMAGE_API_URL}/api/images`);
return res.json();
}
export default async function Home() {
const images = await getImages();
return (
<main>
{images.length === 0 && <h1>No images found!</h1>}
{images.map(image => (
<Image key={image.id} alt={image.alt} src={image.src} width={image.width} height={image.height}/>
)}
</main>
);
}92 Replies
@Ray what is the value of `IMAGE_API_URL`?
Giant AngoraOP
At what point?
https://my-api.localhost:42069 locally
@Giant Angora At what point?
it is an external api?
Giant AngoraOP
http://my-api.default.svc.cluster.local when deployed
or the url for next?
Giant AngoraOP
It's an "external" API
is /api/images a route handler?
ah ok
Giant AngoraOP
By external I mean not part of the Next.js project but still implemented by me
@Giant Angora By external I mean not part of the Next.js project but still implemented by me
is your page static generated?
import { unstable_noStore as noStore } from 'next/cache'
async function getImages() {
noStore()
const IMAGE_API_URL = process.env.IMAGE_API_URL;
// This was added because otherwise the production build would fail due to trying to make a request to this API which is not available at build time.
if (!IMAGE_API_URL) {
return [];
}
const res = await fetch(`${IMAGE_API_URL}/api/images`);
return res.json();
}try this
@Ray is your page static generated?
Giant AngoraOP
So I ended up solving it by forcing it not to be static.
However, now I've got a new issue where attempting to load the image results in the following error response.
export const dynamic = `force-dynamic`;However, now I've got a new issue where attempting to load the image results in the following error response.
$ curl -i https://my-site.com/_next/image?url...
HTTP/2 400
date: Fri, 02 Feb 2024 23:39:46 GMT
strict-transport-security: max-age=31536000; includeSubDomains
"url" parameter is not allowedwhat does the src
src={image.src} look like in the dom?Giant AngoraOP
I have my
next.config.js as so...const url = require('node:url');
/** @type {import('next').NextConfig} */
module.exports = {
output: 'standalone',
images: {
domains: [
'placekitten.com',
url.parse(process.env.AWS_ENDPOINT_URL || 'http://minio.localhost')
.hostname,
url.parse(process.env.IMGPROXY_API_URL || 'http://imgproxy.localhost')
.hostname,
],
},
};I assume it's failing because the domain in the
?url=... parameter isn't allowed?what version of next are you using?
Giant AngoraOP
14.0.3try adding your domain like this in the
next.config.jsGiant AngoraOP
I don't know the domain until runtime
That's why I'm setting it from the environment variable values.
I mean use
remotePatterns instead of domainsGiant AngoraOP
Right
But will it use the value of
process.env.AWS_ENDPOINT_URL?or do I have to wrap the blocks with stuff like this?
https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration
https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration
are you setting the variable after build?
Giant AngoraOP
Yes
Only at runtime
then yes, you gotta use runtime variable
Giant AngoraOP
Are those still valid in 14 / App router?
@Ray https://nextjs.org/docs/app/building-your-application/configuring/environment-variables#runtime-environment-variables
Giant AngoraOP
Yeah the environment variable doesn't need to be client-side
create a function
import { unstable_noStore as noStore } from 'next/cache'
export function getVar(variable: string) {
noStore()
return process.env[variable]
}Giant AngoraOP
This is regarding the
next.config.jsI think we're getting our wires crossed.
Next.js is now on the server-side making the API request and getting the image URL correctly and sending it to the client.
The client is making a request for the image URL sent by the backend.
well I think
next.config.js is runtimetry to copy it to the server
Giant AngoraOP
That request to
/_next/image?url=... is failing I think because the domain of the URL is not whitelisted in the next.config.jsI suspect because after the build
next.config.js is not longer lookup the config from the process.env>This feature is deprecated. We recommend using environment variables instead, which also can support reading runtime values.
where do I copy it the standalone build should have included the
next.config.js that was there at build time no?@Giant Angora That request to `/_next/image?url=...` is failing I think because the domain of the URL is not whitelisted in the `next.config.js`
well if the domain is not whitelist it will say it instead of url parameter isn't allowed
Giant AngoraOP
/app # tree .next/
.next/
├── BUILD_ID
├── app-build-manifest.json
├── app-path-routes-manifest.json
├── build-manifest.json
├── package.json
├── prerender-manifest.js
├── prerender-manifest.json
├── react-loadable-manifest.json
├── required-server-files.json
├── routes-manifest.json
├── server
│ ├── app
│ │ ├── _not-found.html
│ │ ├── _not-found.js
│ │ ├── _not-found.js.nft.json
│ │ ├── _not-found.meta
│ │ ├── _not-found.rsc
│ │ ├── _not-found_client-reference-manifest.js
│ │ ├── api
│ │ │ └── images
│ │ │ ├── route.js
│ │ │ └── route.js.nft.json
│ │ ├── favicon.ico
│ │ │ ├── route.js
│ │ │ └── route.js.nft.json
│ │ ├── favicon.ico.body
│ │ ├── favicon.ico.meta
│ │ ├── index.prefetch.rsc
│ │ ├── page.js
│ │ ├── page.js.nft.json
│ │ ├── page_client-reference-manifest.js
│ │ ├── tos
│ │ │ ├── page.js
│ │ │ ├── page.js.nft.json
│ │ │ └── page_client-reference-manifest.js
│ │ ├── tos.html
│ │ ├── tos.meta
│ │ └── tos.rsc
│ ├── app-paths-manifest.json
│ ├── chunks
│ │ ├── 227.js
│ │ ├── 271.js
│ │ ├── 687.js
│ │ ├── 732.js
│ │ └── font-manifest.json
│ ├── font-manifest.json
│ ├── middleware-build-manifest.js
│ ├── middleware-manifest.json
│ ├── middleware-react-loadable-manifest.js
│ ├── next-font-manifest.js
│ ├── next-font-manifest.json
│ ├── pages
│ │ ├── 404.html
│ │ ├── 500.html
│ │ ├── _app.js
│ │ ├── _app.js.nft.json
│ │ ├── _document.js
│ │ ├── _document.js.nft.json
│ │ ├── _error.js
│ │ └── _error.js.nft.json
│ ├── pages-manifest.json
│ ├── server-reference-manifest.js
│ ├── server-reference-manifest.json
│ └── webpack-runtime.js
└── static
16 directories, 81 files@Ray well if the domain is not whitelist it will say it instead of url parameter isn't allowed
Giant AngoraOP
Okay so what's the problem then? Why is the client unable to make a request for the image?
When if I load that URL from the
?url=... parameter in the browser it loads fineGiant AngoraOP
>2020
Seems like a major oversight
ok i have no idea
Giant AngoraOP
Should it go in the root next to the
server.js created by output: 'standalone'?Giant AngoraOP
I'll connect a debugger and verify this is the issue
So it appears the values that would have been set in
next.config.js at build time are there.But not the value of the
process.env vars that should have overridden it at runtime.Giant AngoraOP
So it seems the config is embedded in the
server.js file as a static JS object.and none of the
process.env carried overSo why doesn't Next.js copy over the entire expression I put in
next.config.js ?const url = require('node:url');
/** @type {import('next').NextConfig} */
module.exports = {
output: 'standalone',
images: {
domains: [
'placekitten.com',
url.parse(process.env.AWS_ENDPOINT_URL || 'http://minio.localhost')
.hostname,
url.parse(process.env.IMGPROXY_API_URL || 'http://imgproxy.localhost')
.hostname,
],
},
};Where is this fabled 'runtime' config.
@Giant Angora So why doesn't Next.js copy over the entire expression I put in `next.config.js` ?
look like it copy the variable when you build
Giant AngoraOP
Yeah exactly along with whatever defaults there are.
If I use this...
The build fails with...
const url = require('node:url');
/** @type {import('next').NextConfig} */
module.exports = {
output: 'standalone',
images: {
domains: [
'placekitten.com',
process.env.AWS_ENDPOINT_URL,
process.env.IMGPROXY_API_URL,
],
},
};The build fails with...
0.697 > next build
0.697
1.069 Invalid next.config.js options detected:
1.069 "images.domains[1]" is missing, expected string
1.069 "images.domains[2]" is missing, expected string
1.069 See more info here: https://nextjs.org/docs/messages/invalid-next-config
1.075  ELIFECYCLE  Command failed with exit code 1could you create a
CNAME and add it to next.config.jsGiant AngoraOP
I tried this but it's still not using
process.env in the server.js of the production build.const url = require('node:url');
const { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_SERVER } = require('next/constants')
/** @type {import('next').NextConfig} */
module.exports = (phase) => {
switch (phase) {
case PHASE_PRODUCTION_SERVER:
return {
output: 'standalone',
images: {
domains: [
`placekitten.com`,
url.parse(process.env.AWS_ENDPOINT_URL).hostname,
url.parse(process.env.IMGPROXY_API_URL).hostname,
]
}
}
case PHASE_DEVELOPMENT_SERVER:
default:
return {
output: 'standalone',
images: {
domains: [
'placekitten.com',
'minio.localhost',
'imgproxy.localhost',
]
}
};
}
}Giant AngoraOP
Looks like people having to do a find and replace of their variables in an entrypoing script.
https://github.com/vercel/next.js/discussions/44628#discussioncomment-7034433
https://github.com/vercel/next.js/discussions/44628#discussioncomment-7034433
Bonkers
@Ray try this
Giant AngoraOP
You don't get it my dude
I don't know what it'll be until runtime
yes
Giant AngoraOP
I'm not going to create CNAMEs adhoc
On the fly
Pointing to 1000s of different domains
The point is to deploy it to an environment
and the environment sets the config
build once run anywhere
Giant AngoraOP
That's because those are used in local development
If I deploy 100 instances of this nextjs applications
and each instance has a different hostname for imgproxy or s3
then I need to be able to set that from an environment variable at runtime
Giant AngoraOP
I ended up patching the built
Then patching the
Which turns into this in the final
🤪
server.js by having the following in my next.config.js./** @type {import('next').NextConfig} */
module.exports = {
output: 'standalone',
images: {
domains: [
'placekitten.com',
'minio.localhost',
'imgproxy.localhost',
"${require('url').parse(process.env.AWS_ENDPOINT_URL).hostname}",
"${require('url').parse(process.env.IMGPROXY_API_URL).hostname}",
],
},
};Then patching the
server.js with sed after it's built to unwrap these strings.RUN sed -i -r 's/"\$\{([^}]*)\}"/\1/g' server.jsWhich turns into this in the final
server.js.// ...
"path": "/_next/image",
"loader": "default",
"loaderFile": "",
"domains": [
"placekitten.com",
"minio.localhost",
"imgproxy.localhost",
require("url").parse(process.env.AWS_ENDPOINT_URL).hostname,
require("url").parse(process.env.IMGPROXY_API_URL).hostname,
],
"disableStaticImages": false,
"minimumCacheTTL": 60,
// ...🤪