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.



