How to deploy a Next.js template
Vercel, containers and plain Node hosts — with the pre-launch checks that catch the usual mistakes.
UT Studio7 min read
Deployment is where a template stops being a repository and starts being a site. Most first deployments fail for one of four reasons, and all four are avoidable in advance.
Build it locally first
Not the dev server — the production build. It type-checks, it lints, it prerenders, and it fails on things the dev server tolerates all day.
pnpm build
pnpm startSet the absolute origin
This is the first of the four. Canonical URLs, OpenGraph tags and the sitemap are all built from one environment variable, and without it every one of them points at localhost.
NEXT_PUBLIC_APP_URL="https://example.com"NEXT_PUBLIC_ variables are baked in at build time
Changing one after the build has no effect. Set it before building, or rebuild after changing it — a runtime restart will not pick it up.
Vercel
- Import the repository.
- If it is a monorepo, set the root directory to the application package.
- Add the environment variables.
- Deploy, then open the deployment and view source to confirm the canonical tag.
Docker
FROM node:22-alpine AS builder
WORKDIR /app
COPY . .
RUN corepack enable && pnpm install --frozen-lockfile && pnpm build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]The two COPY lines after standalone are the ones people forget
Standalone output does not include `.next/static` or `public/`. Miss them and the site boots and renders unstyled — the second of the four failures.
Any Node host
pnpm install --frozen-lockfile
pnpm build
NODE_ENV=production pnpm startPut a reverse proxy in front for TLS and compression. Node can serve directly; it should not have to.
Pre-launch checklist
- Canonical tags resolve to the production origin. View source and read one.
- A URL that does not exist returns your 404 page, not a stack trace.
- The sitemap is reachable and lists what you expect — no admin, no account, no checkout.
- Social preview renders. Paste a URL into a chat app and look.
- Every environment variable the app validates at boot is present. The third failure is a container that starts, fails validation and restarts forever.
- Lighthouse run against the deployed origin.
The fourth failure is deploying the dev server. If your start command is `next dev`, stop and change it.
Templates mentioned here
Everything above is written against real products. These are the ones this page draws on.

