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.
- Push your code to GitHub (or GitLab / Bitbucket).
- Log in to vercel.com, select Import Project, authorize the repository, and select your Next.js repo.
- Vercel automatically detects Next.js, using the default
next buildand Node version strategy to complete deployment; every push to the default branch triggers production deployment, while other branches generate preview URLs.
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):
- No server runtime: Route Handlers, Server Actions, and edge middleware behaviors that depend on a Node server are not supported and need separate evaluation.
- Dynamic routes that aren't enumerated at build time via
generateStaticParamscannot be pre-rendered as individual HTML files (pure export requires all paths to be determined in advance). - No incremental ISR, no per-request server-side logic.
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
NEXT_PUBLIC_*: Accessible in the browser, inlined into the client bundle at build time.- Without prefix: Only available on the Node server side (e.g., inside Server Components, Route Handlers, Server Actions).
- File priority (simplified):
.envโ.env.local(local override, don't commit secrets) โ.env.production/.env.development.
๐ก 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.