Our Practical Next.js 16 Production Checklist for Vercel
A concrete Next.js 16 production checklist from our work at JRV Systems. Learn about Server vs Client Components, caching, ISR, and common production issues.
The Next.js App Router is a significant shift in how we build web applications. It brings powerful features like Server Components and granular caching, but it also introduces new complexities. At JRV Systems, we've moved our new projects—from e-commerce platforms to internal dashboards for Malaysian businesses—to the App Router. This transition required us to build a new set of best practices.
This article shares our internal Next.js 16 production checklist, refined from deploying numerous applications on Vercel. It's a practical guide, not a theoretical one, designed to help you ship with confidence.
Core Decisions: Server vs. Client Components
Your most frequent decision will be whether a component should run on the server or the client. The new paradigm is server-first, which is a major change.
-
Server Components (The Default): Every component you create in the
appdirectory is a Server Component by default. They run exclusively on the server, can beasync, and are perfect for fetching data, accessing databases, or handling sensitive keys. They cannot use hooks likeuseStateoruseEffectbecause they don't run in the browser. -
Client Components (The Exception): You must explicitly opt-in to make a component a Client Component by adding the
'use client'directive at the very top of the file. Use them only when necessary.
Our rule of thumb is simple: a component should only be a Client Component if it needs interactivity. This includes:
- Using
useState,useEffect, oruseContext. - Handling browser events like
onClickoronChange. - Accessing browser-only APIs like
windoworlocalStorage.
To keep your client-side JavaScript bundle small, push Client Components as far down the component tree as possible. Instead of making a whole page a Client Component, isolate the interactive part (like a button or a form) into its own component and import it into a parent Server Component.
Caching: Understanding the New Defaults
The App Router's caching behaviour is aggressive and a common source of confusion. The fetch API is now deeply integrated with Next.js's caching layer. By default, any fetch request is automatically cached indefinitely.
This is great for performance with static content but can lead to stale data if you're not careful. Here’s how to manage it:
-
For dynamic data: If you need fresh data on every request, like checking stock levels for an e-commerce site, use the
cache: 'no-store'option.fetch('https://api.example.com/data', { cache: 'no-store' }) -
For revalidated data (ISR): If data can be stale for a short period, use the
next.revalidateoption. This sets a time-based cache invalidation in seconds. It’s perfect for blog posts or product pages that don't change every second.fetch('https://api.example.com/data', { next: { revalidate: 3600 } })// Revalidate every hour
Understanding this is a critical part of any Next.js 16 production checklist. Always verify the Cache-Control headers in your deployed application to ensure your caching strategy is working as intended.
On-Demand Revalidation for Instant Updates
Time-based revalidation (ISR) is useful, but sometimes you need to update content instantly. For example, when a clinic administrator updates appointment slots in a SaaS we built, or when an e-commerce manager changes a product price. This is where on-demand revalidation comes in.
Next.js provides two primary functions for this, typically triggered by a webhook or a server action:
revalidatePath('/path/to/page'): This purges the cache for a specific URL path. It’s simple and direct.revalidateTag('tag-name'): This is more powerful. You can 'tag' specificfetchrequests with a string. Then, callingrevalidateTagwith that same string will invalidate the cache for every fetch request that used it, across your entire application. This is excellent for updating all pages that display a particular piece of data.
We use revalidateTag extensively at JRV Systems. For an e-commerce client, we tag product data fetches with products. When a price is updated via their admin panel, a webhook calls our revalidation endpoint, which triggers revalidateTag('products'), ensuring all product listings and detail pages show the new price instantly.
Common Production Footguns We've Debugged
Here are four common issues we've encountered when helping clients move to the App Router. Adding these checks to your own process will save you hours of debugging.
-
Forgetting
'use client': A developer tries to useuseStatein a component, and the application breaks with a cryptic error. The fix is almost always adding the'use client'directive. It's the first thing we check. -
Mixing Environment Variables: Remember that Server Components run on the server, but Client Components are rendered on the client. Any environment variable needed in a Client Component must be prefixed with
NEXT_PUBLIC_. Server-only variables (like database connection strings) should not have this prefix and should only be accessed in Server Components to avoid leaking them to the browser. -
Overusing Client Components: A common mistake is making a top-level layout or page a Client Component. This forces all its children to also be Client Components, dramatically increasing the client-side JavaScript bundle size and negating the performance benefits of Server Components.
-
Misunderstanding Dynamic Rendering: Functions like
cookies()orheaders()fromnext/headersor usingsearchParamswill opt a page into dynamic rendering at request time. This is often desired, but if you expect a page to be static and it's not, it's usually because one of these functions is being used somewhere in the component tree.
Final Pre-Launch Checks
Before you merge to main and deploy, run through this final list:
- Check Build Logs: Look for any warnings or errors. Vercel's build output is very informative.
- Audit Client Bundle Size: Use
@next/bundle-analyzerto visually inspect your client-side JavaScript. Are there any unexpectedly large libraries being sent to the browser? - Verify Caching Headers: Use your browser's developer tools to inspect the network tab. Check the
Cache-ControlandX-Vercel-Cacheheaders to confirm your caching strategy is being applied correctly. - Test Revalidation Webhooks: If you use on-demand ISR, trigger your webhooks and confirm that the content updates on the live site as expected.
Following a structured Next.js 16 production checklist like this has helped our team at JRV Systems deliver more stable and performant applications. The App Router has a learning curve, but its capabilities are well worth the investment.