After a bunch of false starts I’ve followed the T3 stack approach to runtime environment variables for my next.js app, particularly to protect my MONGODB_URI
secret by providing it only at runtime on the server.
env/server.js:
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
server: {
NODE_ENV: z.enum(["development", "test", "production"]),
MONGODB_URI: z.string().url(),
},
runtimeEnv: process.env,
});
lib/mongodb.ts:
import { MongoClient } from "mongodb";
import { env } from "@/env/server.mjs";
const uri = env.MONGODB_URI;
const options = {};
if (!uri) {
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"');
}
let client;
let clientPromise: Promise<MongoClient>;
client = new MongoClient(uri, options);
clientPromise = client.connect();
export default clientPromise;
api/data/route.ts:
import clientPromise from "@/lib/mongodb";
export async function GET() {
const client = await clientPromise;
const db = client.db();
const data = await db
.collection("data")
.find({})
.toArray();
return Response.json({
type: "FeatureCollection",
features: data,
});
}
That works locally, but causes a failure within the CI environment, because I’ve not defined the MONGODB_URI
environment variable:
$ next build
❌ Invalid environment variables: { MONGODB_URI: [ 'Required' ] }
⨯ Failed to load next.config.mjs, see more info here https://nextjs.org/docs/messages/next-config-error
> Build error occurred
I thought it would only be needed at runtime, not build. I can work around that by putting a dummy MONGODB_URI
in .env.production
, initially blank, but then looking like a dummy URL to satisfy the zod
rule:
MONGODB_URI=mongodb+srv://host.placeholder.com/db
That works better, but still fails to build locally:
$ yarn run build
yarn run v1.22.19
$ next build
▲ Next.js 14.0.1
- Environments: .env.production
✓ Creating an optimized production build
✓ Compiled successfully
✓ Linting and checking validity of types
Collecting page data ...Error: querySrv ENOTFOUND _mongodb._tcp.host.placeholder.com
at QueryReqWrap.onresolve [as oncomplete] (node:internal/dns/promises:251:17) {
errno: undefined,
code: 'ENOTFOUND',
syscall: 'querySrv',
hostname: '_mongodb._tcp.host.placeholder.com'
}
✓ Collecting page data
Generating static pages (0/7) [= ]
Error occurred prerendering page "/api/data". Read more: https://nextjs.org/docs/messages/prerender-error
Error: querySrv ENOTFOUND _mongodb._tcp.host.placeholder.com
at QueryReqWrap.onresolve [as oncomplete] (node:internal/dns/promises:251:17)
✓ Generating static pages (7/7)
> Export encountered errors on following paths:
/api/data/route: /api/data
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Why are what should be server-only dynamic elements being prerendered at build? Why is Mongo trying to make a connection at build time? (Presumably because of the pre-render). Why are my runtime secrets needed for build? It seems like they’re being baked in, and won’t be overridden by the actual environment.
I’d rather not expose these on my CI environment if I don’t have to. Do I really need to?
2
Answers
Saber BoukHriss's answer got me on the right track, particularly around debugging pre-rendering issues.
Exporting a Route Segment Config from
api/data/route.ts
forces dynamic rather than pre-rendering of the API call. It's a bit of a blunt instrument so the other methods of triggering this (e.g. dynamic request parameters) may be better:api/data/route.ts:
However, attempts are still made to connect to the database as
lib/mongodb.ts
still runs at build. Following some hints in How to avoid executing page code during build?, I wrapped most of the file in a function. This is only then called if a page actually renders at build time, which it shouldn't if declared dynamic.lib/mongodb.ts:
This still leaves
env/server.js
executing and validating at build time. This requires a valid but unused dummy MONGODB_URI in a.env
file.createEnv
has a parameter[skipValidation][4]
which will prevent this. It comes with a warning that it is not encouraged and will lead to your types and runtime values being out of sync, but appears to be intended for build time.env/server.mjs:
You’re encountering a few issues with how your Next.js application handles environment variables, particularly your MongoDB connection URI. Let’s address each of your concerns:
Solution suggestion:
Guard MongoDB Connection: Ensure your MongoDB connection logic is only invoked in server-side contexts (like API routes or getServerSideProps). This way, it won’t be called during the build process. You can use dynamic imports or conditional checks to ensure this.
Environment Variable Defaults: You can provide a default or dummy value for MONGODB_URI for your build environment that satisfies the Zod validation but does not establish an actual connection. This value should only be used in contexts without a real relationship.
Runtime Configuration: Ensure that your actual MONGODB_URI is provided as a runtime environment variable in your production environment. This can be achieved through your deployment configuration, ensuring it’s available when your application needs to connect to MongoDB.
Debugging Pre-rendering Issues: If you’re pre-rendering pages that make server-side calls (like MongoDB), ensure these calls are made in a context that’s not executed during the build process. For example, use getServerSideProps instead of getStaticProps for pages needing real-time server data.
To conclude, your runtime secrets like MONGODB_URI should not be needed for the build unless they are inadvertently used in a build-time context. Adjust your application’s structure to ensure sensitive data is only accessed in server-side or runtime environments, and provide dummy values for build-time validations if necessary.