COREVANIX
  • About
Let's talk
Web development

Next.js 16 production-ready checklist: 25 points before you deploy

25 concrete checks a Next.js 16 project must pass before go-live, covering security, performance, SEO, accessibility and monitoring in one list.

COCorevanix Kft.18 March 202612 min read
Next.js 16 production-ready checklist: 25 points before you deploy

Pre-launch checklist

  1. 01

    Security headers

    CSP, HSTS, X-Frame, Referrer-Policy. Rate-limit on critical routes and never commit .env to git.

  2. 02

    Performance

    SSG or ISR wherever possible, next/image with sizes, bundle-analyzer in CI. Lighthouse 90+ on mobile.

  3. 03

    SEO + a11y

    Metadata, JSON-LD, sitemap and robots. hreflang on multi-locale routes. WCAG AA contrast and full keyboard nav.

  4. 04

    Monitoring

    Sentry with source-map upload, Vercel Analytics or Plausible, plus uptime monitoring wired into Slack.

Next.js 16 has been stable since early 2026, with Turbopack as the default bundler and a set of new App Router features. Still, a production-ready deployment comes with plenty of small details that are easy to miss. This checklist covers 25 concrete items, grouped by category. The points reflect the Next.js 16.2+ state as of Q1 2026; newer releases may change some of this, so it's worth cross-checking the official docs as well.

The 25 points aren't exhaustive — a given project may need more (GDPR, analytics segmentation, A/B testing infrastructure). But with this checklist, launch day won't turn into a "we should have done this too" panic.

What changed in Next.js 16?

Before diving into the checklist, here are a few Next.js 16 changes that affect production deployments:

  • Turbopack by default — the new Rust-based bundler is now stable and on by default, with 4-5x faster builds and a 10x faster dev server.
  • Async Request APIs — cookies(), headers(), params and searchParams are now all async. Upgrading an existing codebase requires rewriting every call site.
  • React 19 by default — stable Server Components, the use() hook, useFormStatus, useActionState.
  • Partial Pre-Rendering (PPR) — still in beta, but works for production-ready use cases.
  • Cache directive — the 'use cache' directive for granular caching.
  • Production-grade Server Actions — secure by default (CSRF protection).

Note: During the upgrade, npx @next/codemod upgrade handles most breaking changes automatically. Manual review is still needed, though — especially for custom middleware and useSearchParams hooks.

Security (6 points)

1. Security headers in next.config.ts

CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy. A minimal setup:

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=63072000; includeSubDomains; preload',
          },
          { key: 'X-Content-Type-Options', value: 'nosniff' },
          { key: 'X-Frame-Options', value: 'DENY' },
          {
            key: 'Referrer-Policy',
            value: 'strict-origin-when-cross-origin',
          },
          {
            key: 'Permissions-Policy',
            value: 'camera=(), microphone=(), geolocation=()',
          },
          {
            key: 'Content-Security-Policy',
            value: [
              "default-src 'self'",
              "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com",
              "style-src 'self' 'unsafe-inline'",
              "img-src 'self' data: https:",
              "font-src 'self' data:",
              "connect-src 'self' https://api.example.com",
              "frame-ancestors 'none'",
            ].join('; '),
          },
        ],
      },
    ];
  },
};

export default nextConfig;

For a CSP nonce solution with dynamic inline scripts: use next/script, or generate a nonce in the middleware.

2. .env.local is NOT committed to git

Covered by a .env* pattern in .gitignore. Only .env.example lives in the repo, as a template.

# .gitignore
.env
.env.local
.env.*.local
.env.production
# .env.example
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://user:pass@host:5432/db
OPENAI_API_KEY=sk-...

As a second layer of secret-leak protection: run git-secrets or gitleaks in CI.

3. CSRF protection for Server Actions

Next.js 15+ ships a built-in Origin header check. Don't disable it manually — Server Actions automatically validate that the request originated from your own domain.

// app/actions/contact.ts
'use server';

import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  message: z.string().min(10).max(2000),
});

export async function submitContact(formData: FormData) {
  const parsed = schema.safeParse({
    email: formData.get('email'),
    message: formData.get('message'),
  });
  
  if (!parsed.success) {
    return { error: 'Invalid input', issues: parsed.error.issues };
  }
  
  // Server-side action — CSRF-protected by default
  await db.contacts.insert(parsed.data);
  return { success: true };
}

4. Rate limiting on critical endpoints

Vercel KV / Upstash Redis plus @upstash/ratelimit. Applies to form submissions, login, and API calls.

// middleware.ts
import { NextResponse } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '60 s'),
  analytics: true,
});

export async function middleware(request) {
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const ip = request.ip ?? '127.0.0.1';
    const { success, limit, remaining } = await ratelimit.limit(ip);
    
    if (!success) {
      return new NextResponse('Too Many Requests', {
        status: 429,
        headers: {
          'X-RateLimit-Limit': limit.toString(),
          'X-RateLimit-Remaining': remaining.toString(),
        },
      });
    }
  }
  return NextResponse.next();
}

5. Escape </ characters in JSON-LD

Injecting JSON-LD into a script tag can create an XSS risk:

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
    __html: JSON.stringify(schema).replace(/</g, '\\u003c'),
  }}
/>

The </ sequence must be escaped, because a </script> substring inside the payload can break out of the script tag.

6. Dependency audit

npm audit should come back clean (0 vulnerabilities). If a Next.js dependency can't be fixed directly, force it with npm overrides.

{
  "overrides": {
    "postcss": "^8.4.31"
  }
}

Plus, in CI:

# .github/workflows/security.yml
- name: Dependency audit
  run: npm audit --audit-level=high
- name: License check
  run: npx license-checker --failOn 'GPL-3.0'

Performance (7 points)

7. Static generation (SSG) wherever possible

generateStaticParams for every dynamic route. ISR (revalidate) when content changes but doesn't need to be real-time. RSC by default; client components only where actually needed.

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({ slug: post.slug }));
}

export const revalidate = 3600; // 1 hour ISR

export default async function BlogPost({ params }) {
  const { slug } = await params;
  const post = await getPostBySlug(slug);
  return <Article post={post} />;
}

8. Using next/image

Use it for every raster image. Set the placeholder="blur" and sizes props.

import Image from 'next/image';

<Image
  src="/blog/images/post-slug/hero.jpg"
  alt="Descriptive alt text"
  width={1200}
  height={630}
  sizes="(max-width: 768px) 100vw, 1200px"
  priority={isAboveFold}
  placeholder="blur"
  blurDataURL="data:image/svg+xml;base64,..."
/>

The priority prop goes on above-the-fold images — it forces eager loading.

9. Font optimization (next/font)

import { Inter, Geist_Mono } from 'next/font/google';

const inter = Inter({
  subsets: ['latin', 'latin-ext'],  // Hungarian characters
  display: 'swap',
  variable: '--font-sans',
});

const geistMono = Geist_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-mono',
});

subsets loads only the characters you actually need. For Hungarian, latin-ext is mandatory (ű, ő, etc.). The self-hosting option ties font loading to build time.

10. Bundle size monitor

The @next/bundle-analyzer plugin. Run a regression check in CI: alert if the bundle grows by more than 20%.

// next.config.ts
import bundleAnalyzer from '@next/bundle-analyzer';

const withBundleAnalyzer = bundleAnalyzer({
  enabled: process.env.ANALYZE === 'true',
});

export default withBundleAnalyzer({...});
ANALYZE=true npm run build
# Opens the bundle tree in the browser

CI integration example with size-limit:

{
  "size-limit": [
    {
      "path": ".next/static/chunks/*.js",
      "limit": "180 KB"
    }
  ]
}

11. Code splitting per route

The App Router splits code by route by default. Avoid the 'use client' directive at the root — scope it narrowly to individual components.

// page.tsx — Server Component (default)
import ClientCounter from './client-counter';

export default async function Page() {
  const data = await fetchServerData();
  return (
    <div>
      <h1>{data.title}</h1>
      {/* Only this component becomes client-side */}
      <ClientCounter initial={data.count} />
    </div>
  );
}
// client-counter.tsx — Client Component
'use client';

import { useState } from 'react';

export default function ClientCounter({ initial }) {
  const [count, setCount] = useState(initial);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

12. Image lazy loading

loading="lazy" is the default on next/image. Set priority explicitly for above-the-fold images. Every other image below the fold doesn't load until it approaches the viewport.

13. Caching strategy

  • Static assets: 1 year (Cache-Control: public, max-age=31536000, immutable)
  • API responses: revalidate on a per-use-case basis
  • HTML: server-driven, zero browser cache, long CDN cache
  • Server Component fetch: cache: 'force-cache' or 'no-store'
// Server Component
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 }, // 1 hour ISR
  });
  return res.json();
}

In Next.js 16, the 'use cache' directive gives even more granular control:

async function getProductList() {
  'use cache';
  cacheLife('hours');
  cacheTag('products');
  return db.products.findAll();
}

SEO (5 points)

14. Metadata on every page

import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Page title — Brand',
  description: 'Page description ~150 char',
  openGraph: {
    title: 'OG title',
    description: 'OG description',
    images: ['/og-image.jpg'],
    locale: 'hu_HU',
    type: 'website',
  },
  twitter: {
    card: 'summary_large_image',
    title: 'Twitter title',
    description: 'Twitter description',
    images: ['/twitter-image.jpg'],
  },
  alternates: {
    canonical: 'https://example.com/page',
    languages: {
      'hu-HU': 'https://example.com/hu/page',
      'en-US': 'https://example.com/en/page',
    },
  },
};

Dynamic metadata:

export async function generateMetadata({ params }): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPostBySlug(slug);
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: { images: [post.image] },
  };
}

15. Sitemap.xml

src/app/sitemap.ts, using Next.js's native support. Include every static and dynamic route.

// app/sitemap.ts
import type { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://example.com';
  const posts = await getAllPosts();
  
  const blogUrls = posts.map((p) => ({
    url: `${baseUrl}/blog/${p.slug}`,
    lastModified: p.updatedAt,
    changeFrequency: 'weekly' as const,
    priority: 0.7,
  }));
  
  return [
    { url: baseUrl, priority: 1.0 },
    { url: `${baseUrl}/blog`, priority: 0.8 },
    ...blogUrls,
  ];
}

16. Robots.txt

As src/app/robots.ts. On dev/staging environments, set Disallow: / for every user agent.

// app/robots.ts
import type { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  const isProduction = process.env.NEXT_PUBLIC_ENV === 'production';
  
  if (!isProduction) {
    return {
      rules: { userAgent: '*', disallow: '/' },
    };
  }
  
  return {
    rules: [
      { userAgent: '*', allow: '/', disallow: ['/api/', '/admin/'] },
    ],
    sitemap: 'https://example.com/sitemap.xml',
  };
}

17. JSON-LD schema

Add Organization, BlogPosting, FAQPage or Product schema as relevant. Validate with the Schema Markup Validator.

const schema = {
  '@context': 'https://schema.org',
  '@type': 'BlogPosting',
  headline: post.title,
  datePublished: post.date,
  author: {
    '@type': 'Organization',
    name: 'Corevanix',
  },
  image: post.coverImage,
};

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
    __html: JSON.stringify(schema).replace(/</g, '\\u003c'),
  }}
/>

18. hreflang on multi-locale routes

Set alternates.languages in the metadata on every multi-locale page. Tested with Google Search Console.

Accessibility (4 points)

19. Keyboard navigation

Every interactive element is reachable by keyboard. The focus-visible ring is visible (focus-visible:ring-2).

<button
  className="rounded bg-accent px-4 py-2 focus-visible:outline-none
             focus-visible:ring-2 focus-visible:ring-accent-primary
             focus-visible:ring-offset-2"
>
  Click me
</button>

20. ARIA labels on forms and navigation

aria-label on every icon button, aria-labelledby on sections, aria-expanded on accordions.

<button aria-label="Close" onClick={onClose}>
  <X aria-hidden="true" />
</button>

<nav aria-label="Main navigation">
  <ul>...</ul>
</nav>

<button aria-expanded={isOpen} aria-controls="menu">
  Menu
</button>

21. Color contrast WCAG AA

Text vs. background: at least 4.5:1, large text 3:1. Lighthouse audits this automatically; also check manually with a tool like the WebAIM Contrast Checker.

Check both variants if you're using Tailwind's dark/light mode.

22. Skip-to-content link

<a
  href="#main"
  className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-4
             focus:bg-accent-primary focus:px-4 focus:py-2 focus:rounded"
>
  Skip to main content
</a>

<main id="main">...</main>

Monitoring (3 points)

23. Error tracking — Sentry

Set up @sentry/nextjs. Upload source maps during the Vercel build. Track releases by commit SHA.

// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 0.1,
  environment: process.env.NEXT_PUBLIC_ENV,
  release: process.env.NEXT_PUBLIC_COMMIT_SHA,
});
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1,
  replaysSessionSampleRate: 0.05,
  replaysOnErrorSampleRate: 1.0,
  integrations: [Sentry.replayIntegration()],
});

24. Web Analytics

Vercel Analytics (free tier), Plausible, or PostHog. Prefer GDPR-compliant, cookieless options.

import { Analytics } from '@vercel/analytics/next';
import { SpeedInsights } from '@vercel/speed-insights/next';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
        <SpeedInsights />
      </body>
    </html>
  );
}

25. Uptime monitoring

UptimeRobot (free), Better Stack, or Vercel's built-in monitoring. Slack/email alerts on downtime.

Recommended setup:

  • Homepage check: 5 min interval
  • Critical API check: 1 min interval
  • SSL-cert monitoring: 24h interval
  • Alert: Slack + email + PagerDuty for production-critical incidents

Pre-deployment final check

Before you push to main:

npm run lint            # 0 error
npm run typecheck       # 0 error  
npm run build           # success, no warning
npm run start           # smoke test localhost
npx unlighthouse        # Lighthouse 90+ across every category

Lighthouse target

Category Target Ideal
Performance 90+ 95+
Accessibility 95+ 100
Best Practices 95+ 100
SEO 95+ 100

Measured on a mid-range mobile device. Almost everyone hits 100 on a laptop — mobile is what's release-critical.

Core Web Vitals targets

Metric Good Needs improvement Poor
LCP < 2.5s 2.5-4.0s > 4.0s
CLS < 0.1 0.1-0.25 > 0.25
INP < 200ms 200-500ms > 500ms

Bonus points (mandatory in production)

26. Error boundaries

// app/error.tsx
'use client';

import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';

export default function Error({ error, reset }) {
  useEffect(() => {
    Sentry.captureException(error);
  }, [error]);
  
  return (
    <div>
      <h2>Something went wrong</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

27. Custom 404 page

// app/not-found.tsx
import Link from 'next/link';

export default function NotFound() {
  return (
    <div>
      <h2>Page not found</h2>
      <Link href="/">Back to homepage</Link>
    </div>
  );
}

28. Loading states

// app/blog/[slug]/loading.tsx
export default function Loading() {
  return <BlogPostSkeleton />;
}

29. Preconnect to critical 3rd parties

// app/layout.tsx
<head>
  <link rel="preconnect" href="https://fonts.googleapis.com" />
  <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
  <link rel="dns-prefetch" href="https://www.googletagmanager.com" />
</head>

30. Vercel project config

{
  "framework": "nextjs",
  "buildCommand": "npm run build",
  "regions": ["fra1"],
  "github": {
    "silent": false
  }
}

fra1 (Frankfurt) region for EU data residency. Multi-region only if you have significant global traffic.

Official docs and further reading

  • Next.js 16 documentation — official guide
  • Next.js Production Checklist — Vercel official
  • Web.dev Core Web Vitals — Google CWV guide
  • OWASP Top 10 — security baseline
  • WCAG 2.2 Guidelines — accessibility standard
  • Schema.org JSON-LD — structured data

Related articles from us: Headless CMS in Hungary — content management alongside Next.js. Improving webshop conversion rate — e-commerce performance. 7 defenses against LLM hallucinations — if you're building an AI feature into Next.js.

Closing thoughts

The 25 points aren't exhaustive — a given project may need more. But with this checklist, launch day won't turn into a "we should have done this too" panic.

A pre-launch audit is a 1-2 day process: work through all 25+5 points, measure, and fix the gaps. Don't leave this for the first week of hyper-care — it belongs in the sprint during the final week before launch.

If you're planning a Next.js project for production, let's talk through the deployment strategy — we have a proven approach for every one of the 25 points. On the first release, our partner developer personally walks through the checklist with you, so the handover goes cleanly.

Tags
  • #Next.js
  • #Production
  • #Deployment
  • #Performance
  • #SEO
  • #Security
ShareLinkedInX

About the author

CO

Corevanix Kft.

Technology partner

Budapest-based technology partner — SAP/ERP integration, web development, AI automation and mobile app development. We work inside the client’s own environment, and the delivered code belongs entirely to the client.

Planning a project?

Let's talk in a 30-minute call.

Book a callSend an email

Related articles

  • Headless CMS in Hungary: Sanity vs Strapi vs Contentful in 2026
    Web development

    Headless CMS in Hungary: Sanity vs Strapi vs Contentful in 2026

    A deep dive into three headless CMS options: Sanity, Strapi and Contentful. Pricing, real-time preview, localisation and five Hungarian-market use cases.

    12 March 202611 min read
    Read more
  • Improving webshop conversion rate: 10 technical fixes that grow revenue
    Web development

    Improving webshop conversion rate: 10 technical fixes that grow revenue

    Ten concrete technical optimisations that measurably lift webshop conversion: performance, UX, trust signals, checkout flow and A/B-testable elements.

    5 March 202611 min read
    Read more
Where do we start?

Where do we start?

  • I'm building a new product.

    Web / app development
  • I have an existing system.

    SAP / ERP integration
  • I want to automate a process.

    AI automation
  • I just want advice.

    Discovery call

Services

  • Enterprise systems
  • Web development
  • AI automation
  • Mobile app development

Tech Stack

  • Web
  • Mobile
  • SAP / ERP
  • AI platform

Company

  • About
  • Case studies
  • Blog
  • Contact

Legal

  • Privacy policy
  • Legal notice
  • Cookie policy
COREVANIX

Corevanix Kft. is a Budapest-based technology partner: SAP/ERP integration, web development, AI automation and mobile app development for companies in Hungary and the EU.

© 2026 Corevanix Kft. All rights reserved.

info@corevanix.com

Headquarters: Budapest, Hungary