โ† Back to Index

Chapter 8: Deployment & Optimization

Vercel/Docker Deployment, SEO Optimization & Performance Tuning

1. Vercel Deployment

Vercel is built by the Next.js team and is the hosting platform with the highest compatibility with Next.js: build pipelines, edge networks, and preview environments are deeply integrated with the framework, allowing most projects to go live with nearly zero ops.

Environment Variables: Add key-value pairs in project Settings โ†’ Environment Variables, differentiated by Production / Preview / Development. Name them consistently with local setup (e.g., DATABASE_URL, NEXT_PUBLIC_SITE_URL); redeploy for new variables to take effect.

Custom Domain: Add your domain in Domains, configure A / CNAME records at your DNS provider as instructed; Vercel automatically issues and renews HTTPS certificates.

๐Ÿ’ก Key Point

Vercel automatically detects Next.js project structure and selects optimized build and caching strategies, achieving near zero-config for common scenarios; focus your effort on business logic and next.config.

2. Docker Deployment

When you need self-hosting (private cloud, Kubernetes, bare metal), Docker is the universal delivery format. Next.js 15 recommends using output: 'standalone' โ€” the build output contains only the minimum files needed to run, making images smaller and startup faster.

next.config.ts snippet:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "standalone",
};

export default nextConfig;

Multi-stage Dockerfile example (Node 20, adjust COPY paths for your project):

# syntax=docker/dockerfile:1

FROM node:20-alpine AS base
RUN apk add --no-cache libc6-compat
WORKDIR /app

FROM base AS deps
COPY package.json package-lock.json* ./
RUN npm ci

FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

FROM base AS runner
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

docker-compose.yml example (YAML, used with docker compose up):

version: "3.8"

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production

Build and run:

docker build -t my-next-app .
docker run --rm -p 3000:3000 my-next-app

# ๆˆ–ไฝฟ็”จ compose
docker compose up --build

3. Static Export

If your site is entirely static content (docs, marketing pages), you can use static export to generate pure HTML/CSS/JS, hostable on any static server or CDN.

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "export",
  // ๅฏ้€‰๏ผšๆ— ๅฐพ้ƒจๆ–œๆ ็ญ‰
  // trailingSlash: true,
};

export default nextConfig;

Limitations (important to know):

Suitable scenarios: Static blog mirrors, product landing pages, offline-distributable frontend packages. Build output goes to out/, deployable to GitHub Pages, S3, Cloudflare Pages, etc.

4. Metadata API (SEO)

App Router uses the Metadata API to describe page titles, descriptions, Open Graph, Twitter Cards, etc. Next injects these into <head> on the server, benefiting SEO and social sharing.

Static metadata (app/layout.tsx or page):

import type { Metadata } from "next";

export const metadata: Metadata = {
  title: {
    default: "ๆˆ‘็š„็ซ™็‚น",
    template: "%s | ๆˆ‘็š„็ซ™็‚น",
  },
  description: "ๅŸบไบŽ Next.js 15 ็š„ๅ…จๆ ˆ็ซ™็‚น",
  openGraph: {
    title: "ๆˆ‘็š„็ซ™็‚น",
    description: "ๅŸบไบŽ Next.js 15 ็š„ๅ…จๆ ˆ็ซ™็‚น",
    url: "https://example.com",
    siteName: "ๆˆ‘็š„็ซ™็‚น",
    locale: "zh_CN",
    type: "website",
  },
  twitter: {
    card: "summary_large_image",
    title: "ๆˆ‘็š„็ซ™็‚น",
    description: "ๅŸบไบŽ Next.js 15 ็š„ๅ…จๆ ˆ็ซ™็‚น",
  },
  robots: { index: true, follow: true },
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh-CN">
      <body>{children}</body>
    </html>
  );
}

generateMetadata (dynamic pages, e.g., blog posts):

import type { Metadata } from "next";

type Props = { params: Promise<{ slug: string }> };

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPostBySlug(slug); // ไฝ ็š„ๆ•ฐๆฎๅฑ‚

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: "article",
      publishedTime: post.publishedAt,
    },
  };
}

export default async function Page({ params }: Props) {
  const { slug } = await params;
  const post = await getPostBySlug(slug);
  return <article><h1>{post.title}</h1></article>;
}

async function getPostBySlug(slug: string) {
  return { title: "็คบไพ‹", excerpt: "ๆ‘˜่ฆ", publishedAt: new Date().toISOString() };
}

title.template: Setting template: '%s | Brand Name' in layout means child pages only need title: 'Article Title' to get the complete title.

Next.js 15 can export convention functions from app/robots.ts and app/sitemap.ts to auto-generate /robots.txt and /sitemap.xml:

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

export default function robots(): MetadataRoute.Robots {
  return {
    rules: { userAgent: "*", allow: "/", disallow: "/private/" },
    sitemap: "https://example.com/sitemap.xml",
  };
}
// app/sitemap.ts
import type { MetadataRoute } from "next";

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    { url: "https://example.com", lastModified: new Date(), changeFrequency: "weekly", priority: 1 },
    { url: "https://example.com/blog", lastModified: new Date(), changeFrequency: "weekly", priority: 0.8 },
  ];
}

5. Common next.config.ts Settings

Centrally manage redirects, rewrites, security headers, image domains, and environment variable injection in the root next.config.ts.

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  async redirects() {
    return [{ source: "/old-blog/:slug", destination: "/blog/:slug", permanent: true }];
  },
  async rewrites() {
    return [{ source: "/proxy-api/:path*", destination: "https://api.example.com/:path*" }];
  },
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          { key: "X-Frame-Options", value: "DENY" },
          { key: "X-Content-Type-Options", value: "nosniff" },
        ],
      },
    ];
  },
  images: {
    remotePatterns: [{ protocol: "https", hostname: "cdn.example.com", pathname: "/images/**" }],
  },
  env: {
    CUSTOM_BUILD_ID: process.env.VERCEL_GIT_COMMIT_SHA ?? "local",
  },
};

export default nextConfig;
Config Option Purpose
redirects HTTP redirects (301/302), suitable for path changes during redesigns and route merging.
rewrites URL rewriting: browser URL stays the same, Next reverse-proxies to another origin.
headers Add response headers to matching paths (security, caching, CORS, etc.).
images.remotePatterns Allowed remote image domains and path patterns for next/image optimization.
env Inline values into client bundle (public info); never put sensitive data here.

6. Performance Optimization

Dynamic Import (next/dynamic) loads heavy components on demand (charts, maps, rich text editors), reducing first-screen JS.

import dynamic from "next/dynamic";

const HeavyChart = dynamic(() => import("@/components/HeavyChart"), {
  loading: () => <p className="p-4">ๅŠ ่ฝฝไธญโ€ฆ</p>,
  ssr: false,
});

export default function Page() {
  return (
    <main>
      <h1>ไปช่กจ็›˜</h1>
      <HeavyChart />
    </main>
  );
}

In pure client subtrees, you can also use React.lazy + Suspense (ensure it doesn't break RSC boundary rules; typically used inside Client Components):

"use client";

import { lazy, Suspense } from "react";

const Modal = lazy(() => import("./Modal"));

export function Panel() {
  return (
    <Suspense fallback={<span>โ€ฆ</span>}>
      <Modal />
    </Suspense>
  );
}

Bundle Analyzer: Install @next/bundle-analyzer and enable analysis via environment variable:

npm install -D @next/bundle-analyzer
// next.config.ts
import type { NextConfig } from "next";
import bundleAnalyzer from "@next/bundle-analyzer";

const withAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === "true" });

const nextConfig: NextConfig = {};

export default withAnalyzer(nextConfig);
ANALYZE=true npm run build

Images: Continue using next/image for automatic srcset, lazy loading, and size placeholders. Fonts: Use next/font (e.g., Inter, Noto_Sans_SC) to self-host font files, reducing layout shift and external link requests.

For those with React experience

Performance optimization is still "measure first, optimize later": use Analyzer to find large bundles, then use dynamic / lazy to split them; leave images and fonts to Next's built-in solutions โ€” usually more reliable than hand-written <img> + public CDN fonts.

7. Common Environment Variables

๐Ÿ’ก Key Point

Variables with the NEXT_PUBLIC_ prefix are included in client-side JavaScript โ€” any user can see them in DevTools. Never store API keys, database connection strings, or other sensitive information in them.

8. Chapter Summary & Tutorial Conclusion

Deployment Paths

Vercel + Git for continuous deployment; self-hosted with standalone + Docker; pure static with output: 'export' โ€” know the capability boundaries.

SEO

metadata / generateMetadata, OG/Twitter, robots.ts and sitemap.ts form complete search and sharing metadata.

Configuration & Performance

Manage redirects, rewrites, headers, and image domains in next.config; control bundle size with dynamic / lazy and Bundle Analyzer.

Environment Variables

Distinguish between NEXT_PUBLIC_ and server secrets; understand the purpose and security boundaries of each .env* file.

๐ŸŽ‰ Congratulations!

You've completed all core chapters of this quick tutorial: from setup, routing, rendering, data, styling, and state to database and deployment optimization.

Recommended Next Steps