Next.js

Next.js Vercel Deployment: 9 Mistakes I Made in Production

Hard-won Next.js Vercel deployment lessons from shipping real production apps — env vars, ISR, edge runtime, bandwidth bills, and gotchas to fix.

Smit Parekh26 April 20267 min read
Next.js Vercel Deployment: 9 Mistakes I Made in Production

Vercel makes deploying a Next.js app feel like magic. Push to main, get a URL, ship. The official docs cover the happy path very well, and you should read them. But the happy path is not where you lose money, leak data, or get paged at 2 a.m.

This post is the other side of that story. Nine specific things that went wrong on real Next.js apps I shipped to Vercel in the last twelve months — what broke, why the docs didn't save me, and the fix. No screenshots of the dashboard, no "step 1: click deploy." Just the parts I wish someone had warned me about.

1. Treating preview

deployments as private

Every push to a non-production branch gets a public preview URL. "Public" is the part most people miss. The URL is unguessable, but it is not authenticated. I once shipped a preview of a client dashboard with seeded production-like data, posted the URL in a Slack channel, and watched it get crawled by a link unfurler that cached a thumbnail with a real customer's name on it.

Fix: turn on Vercel Authentication for preview deployments in project settings, or wrap previews in middleware that checks for a header your team sets. Treat every preview URL as if it will be indexed, screenshotted, and shared.

2. Confusing build-time and

runtime environment variables

This one bites everyone once. NEXT_PUBLIC_* variables are inlined at build time. If you change them in the Vercel dashboard and don't redeploy, nothing happens. Worse — if you accidentally prefix a secret with NEXT_PUBLIC_, it ends up in the client bundle. I have seen Stripe restricted keys ship to a browser this way.

Fix: audit your .env files. Anything sensitive should never have the NEXT_PUBLIC_ prefix. Add a CI check that greps the built .next output for known secret patterns before promoting to production.

3. Assuming ISR "just works" across regions

Incremental Static Regeneration is brilliant until you realize the cache is per-region. A user in Mumbai might see a stale page for an hour after a user in Frankfurt triggered a revalidation. For a marketing site this is fine. For a pricing page tied to a promo, it is not.

Fix: for content that must be globally consistent within seconds, use on-demand revalidation via revalidateTag or revalidatePath triggered from your CMS webhook, and accept that truly real-time data should not be statically generated at all. Put it on a server component with dynamic = 'force-dynamic' or move it to an API route.

4. Edge runtime as a default

The edge runtime is fast, cheap, and limited. No Node APIs, no native modules, a small bundle budget. I migrated a route to edge to shave latency, then spent a day debugging why crypto.createHash worked locally and failed in production. It worked locally because Next.js was using the Node runtime in dev.

Fix: pick the runtime per route based on what the route actually needs. Database clients that ship a TCP driver, image processing, anything that touches the filesystem — keep those on Node. Use edge for lightweight read paths, auth checks, and personalization.

5. Letting the Image component DDoS your wallet

The next/image component on Vercel is metered by source images transformed and bandwidth served. A blog with twelve images and a hundred visitors a day costs nothing. A programmatic SEO site with ten thousand pages, each with a dynamic OG image, can quietly burn through the included quota in a weekend.

Fix: check your Vercel usage dashboard weekly during the first month after launch. Cap image widths in your sizes prop so you are not serving a 3840px image to a phone. For high-volume static images, consider hosting transformed variants on Cloudflare R2 or a similar object store and skipping the optimizer entirely.

6. Middleware running on every single

request

Middleware is powerful and it runs before the cache. Add a heavy database call there and you have just removed the benefit of caching from your entire app. I once added a feature-flag lookup in middleware "temporarily." Three weeks later our p95 had crept from 90 ms to 380 ms.

Fix: keep middleware to cheap, cache-friendly work — geo headers, redirects, auth cookie checks against a JWT. Anything that touches a database belongs in a server component or route handler where the response can be cached. Use the matcher config to scope middleware to the paths that actually need it.

7. Forgetting that output: 'standalone' is for self-hosting

If you copy a next.config.js from a tutorial, double-check it doesn't have output: 'standalone' set. That mode is for Docker and self-hosting; on Vercel it can produce confusing build artifacts and disable some platform features. The deploy will succeed, but you'll wonder why ISR or image optimization behaves oddly.

Fix: delete the output field unless you know you need it. Vercel handles the build output format on its own.

8. Skipping the production smoke test on a real device

Vercel previews look great on your laptop. Then a user opens it on a three-year-old Android over a flaky 4G connection and gets a 12-second LCP. The default Next.js setup is fast, but the moment you add a heavy client component, a third-party tag manager, or a hero video, real-world performance can collapse.

Fix: before you flip a deployment to production, run it through PageSpeed Insights on the mobile tab and look at the field data, not just the lab score. If you don't have field data yet, throttle your own browser to slow 4G and a 4x CPU slowdown. The number that matters is the one your users will actually see.

9. No alerting on deploys that succeed but break

A deploy that fails the build is loud — Vercel emails you. A deploy where the build succeeds, the home page loads, and the checkout silently 500s is silent. The dashboard shows green. Customers know before you do.

Fix: wire up at least one synthetic check that hits a critical user path after every production deploy. A simple GitHub Action that runs Playwright against the production URL post-deploy, or a third-party uptime tool that runs a multi-step transaction every five minutes, will catch the failures the platform doesn't see.

What the official docs are good for, and what they

aren't

Vercel's documentation is excellent for how the platform works. It is not, and was never going to be, a guide tohow your specific app will fail. Those failures come from the seams — between your framework version, your data layer, your team's habits, and the assumptions baked into a default configuration.

If you are deploying your first Next.js app to Vercel today, follow the official deployment guide for the steps. Then come back to a post like this one, or write your own version after your first incident, and treat each item here as a checklist for your second deploy.

The gap between deployedanddeployed well is where senior engineering work lives. The platform handles the first part. The second part is on you.

If you want a second pair of eyes on a Next.js deployment before it ships, that is exactly the kind of work I do — get in touch via the hire me page.

nextjsverceldeploymentproductiondevops

Need a Next.js developer?

95+ Lighthouse, SEO-first App Router builds — from Server Components to production deployLet's talk about your project.