Next.js, adding features just to development mode
By Flavio Copes
Learn how to enable features only in development mode in Next.js by checking process.env.NODE_ENV, so API routes, pages, or JSX stay hidden in production.
To run a feature only in development mode in Next.js, check the value of process.env.NODE_ENV. When it’s 'development', you’re running locally. When it’s 'production', you’re on the live site.
Some sites/apps I work on have 2 modes. One is the development mode, the other is production, the live version.
With Next.js I find this very easy to handle because Next.js sets NODE_ENV for you. It’s 'development' when you run npm run dev, and 'production' when the site is built with next build and served with next start. No configuration needed, and it works the same way in Next.js 16, with both the Pages Router and the App Router.
Hiding an API route in production
I might have an API route that should not be public, and at the top of it, I add
if (process.env.NODE_ENV != 'development') return null
so it does not work in production.
Be careful here. If an API route returns without sending a response, the request hangs and Next.js logs a warning. A cleaner version sends a 404:
export default function handler(req, res) {
if (process.env.NODE_ENV !== 'development') {
return res.status(404).end()
}
res.status(200).json({ orders: 120 })
}
To anyone visiting the production site, the route looks like it doesn’t exist at all.
This is a Pages Router API route, a file like pages/api/orders.js. Next.js 16 still supports them.
With the App Router
In the App Router, the same endpoint is a Route Handler. You create a route.js file in a folder under app, for example app/api/orders/route.js, and export a function named after the HTTP method.
Route Handlers don’t get a res object. They return a Response, so in production the check returns an empty one with a 404 status:
export function GET() {
if (process.env.NODE_ENV !== 'development') {
return new Response(null, { status: 404 })
}
return Response.json({ orders: 120 })
}
Each HTTP method is its own function, so a check in GET doesn’t protect POST. If the file also exports POST or other methods, add the same check at the top of each one.
Hiding a page
The same applies to a page component, which will render a blank page if accessed in production:
export default function Debug() {
if (process.env.NODE_ENV !== 'development') return null
return <div>Internal debug page</div>
}
In the App Router you can do better than a blank page. Call notFound() from next/navigation, and production visitors get the regular 404 page:
import { notFound } from 'next/navigation'
export default function Debug() {
if (process.env.NODE_ENV !== 'development') notFound()
return <div>Internal debug page</div>
}
Showing JSX only in development
I use the same technique to add JSX to a component only in development mode:
{
process.env.NODE_ENV == 'development' && <div>hi</div>
}
This is handy for debug output you want to see while working, like the current state of a form, with no risk of shipping it to users.
As a bonus, Next.js replaces process.env.NODE_ENV in the client bundle with its actual value at build time. In production the condition is always false, so the dev-only JSX gets stripped from the bundle entirely, and users don’t even download it.
Don’t set NODE_ENV yourself
Don’t put NODE_ENV in a .env file. Next.js manages it and expects one of development, production or test. A different value triggers a warning, and forcing development on a production server changes how Next.js behaves.
If you need your own flag, for example to mark a staging environment, create a separate variable like NEXT_PUBLIC_STAGING and check that instead.
Want me to talk about your product? You can sponsor this site.
Related posts about next: