Four production-only bugs `next dev` will never show you
Canonicals pointing at localhost, a URL frozen at build time, uploads that 404, a security policy that silently breaks the page. Four Next.js bugs invisible in development, and how to catch them.
By Tenovates4 min read
Written 2026-09 · Next.js 16
The problem
The development server is built for speed and forgiveness: it rebuilds on every change, reads environment files directly, serves new files the moment they appear, and relaxes several security settings. A production build does none of that. Some bugs only exist in the gap between the two — and because everyone tests in development, they reach production untouched.
Here are four of the most common in Next.js projects.
1. Canonicals that point at localhost
Symptom : in production, the page's <link rel="canonical"> and its
social-sharing image point at your local development address, on port
3000.
Why : relative URLs in metadata (a canonical of /about, an image
of /og.png) are resolved against metadataBase. When it isn't set,
Next.js falls back to a local default. In development that looks right;
in production it tells search engines the real page lives somewhere
else.
Fix : set metadataBase once, in the root layout, from the site's
public URL — and fail loudly if it's missing, rather than falling back to
a local address, which is exactly the bug:
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
if (!siteUrl) throw new Error("NEXT_PUBLIC_SITE_URL is not set");
export const metadata = {
metadataBase: new URL(siteUrl),
};
Check : on the production site, read the canonical of a real page
(curl -sL <url> | grep canonical) and make sure it shows your domain.
2. A URL frozen at build time
Symptom : you changed the site URL in the server's environment and restarted — the site still uses the old value.
Why : variables prefixed NEXT_PUBLIC_ are inlined into the
bundle when the site is built. Changing them afterwards has no effect
until the next build. In container builds, there's a second trap: if the
environment file isn't part of the build context (and it shouldn't be —
it holds secrets), the variable is simply empty at build time.
Fix : pass public values to the build explicitly (a build argument, or your platform's build-time variables), and rebuild when they change. Keep secrets out of the build context entirely.
Check : after deploying, look at a value that depends on the variable (a canonical, an absolute link) on the live site.
3. Uploads that 404 after the deploy
Symptom : an image uploaded from the admin appears in the admin, but
returns 404 on the public site — while images that existed at build
time work fine.
Why : the production server can index the public/ folder when it
starts; a file written there later may not be served by it at all.
In development, new files are picked up immediately, so the bug never
shows.
Fix : don't rely on public/ for files that appear at run time.
Serve them through a route handler that reads the upload directory
(validating the path strictly), or from a web server or storage service
configured to serve that directory directly.
Check : on the live site, upload a file after deploying and open its URL.
4. A security policy that silently breaks the page
Symptom : after adding a Content Security Policy, pages look normal but nothing is interactive — buttons do nothing, forms don't submit.
Why : Next.js pages include inline scripts that hydrate the page. A
strict policy that forbids inline scripts blocks them. The HTML still
renders, so the page looks fine; the browser console is the only place
that says otherwise. Development mode differs again (it needs eval for
fast refresh), so a policy tested only in development can fail both ways.
Fix : build the policy for production explicitly — allow what the framework needs (or use nonces), add each third-party origin you actually load — and test it on a production build.
Check : in a real browser, open a page with a form, click something interactive, and read the console for policy violations. Then trigger one violation on purpose to confirm your check would catch it.
The take-away
Test the production build, at the production address, before trusting it. All four bugs are invisible in development and obvious within minutes on the real thing.
Checklist
Production-only checks for a Next.js site
1. Canonical of a real page → your domain, not localhost (metadataBase)
2. NEXT_PUBLIC_* values → provided at build time ; rebuild after changing
3. File uploaded after the deploy → actually served
4. CSP → pages hydrate, no console violation ; a deliberate violation is caught