Deploy and Launch
Contents
Phase 7: GitHub setup
From the project root ({{name}}/):
git init
git add -A
git commit -m "initial commit"
git branch -M main
gh repo create {{repo}} --public --source=. --remote=origin --pushCreates the repo and pushes in one step via the GitHub CLI (gh). If gh is unavailable:
git remote add origin https://github.com/{{repo}}.git
git push -u origin maingit add -A is safe here because the tree is fresh and both .gitignore files are in place. Confirm git status shows no .next/, node_modules/, or next-env.d.ts before committing.
Phase 7: Vercel deployment
Via the Vercel CLI:
pnpm dlx vercel --yes
pnpm dlx vercel --prodOr via the dashboard:
- Go to vercel.com/new and add a new project.
- Import the GitHub repo (
{{repo}}). - Vercel auto-detects the turborepo, the Next.js app in
apps/web, and pnpm (frompnpm-lock.yaml). To build with the exact pnpm version inpackageManagerrather than the one Vercel infers from the lockfile, set theENABLE_EXPERIMENTAL_COREPACK=1environment variable on the project. - Deploy.
Add custom domain {{domain}} (dashboard Settings > Domains, or pnpm dlx vercel domains add {{domain}}).
On a 404 or wrong app, set the project Root Directory to apps/web (dashboard Settings > General > Root Directory) and redeploy; Vercel does not always infer the app location in a fresh turborepo.
Optional, once a second workspace exists: add apps/web/vercel.json so Vercel skips builds that turbo can prove did not touch the app.
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"ignoreCommand": "pnpm dlx turbo-ignore"
}turbo-ignore exits 0 (skip the build) when no file in the workspace or its dependencies changed since the last deploy. Prefix it with a preview guard (if [ "$VERCEL_ENV" = "preview" ]; then exit 0; fi;) only if the project deliberately does not build previews. A root .vercelignore listing .turbo, node_modules, *.log, and .git trims the upload; excluding .git means build-time tools cannot read the commit SHA, so pass VERCEL_GIT_COMMIT_SHA to anything that wants a release version.
Never assume {{name}}.vercel.app is yours. The namespace is global and first-come, so a name can already point at an unrelated site; use only the alias Vercel confirms for the project.
Verify: https://{{domain}} loads the default Next.js page.
CI
Vercel builds are the only gate otherwise, and a failed production build is found after merge. Add .github/workflows/check.yml so every pull request runs the same checks as the hook plus a production build:
name: Check
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
cache: pnpm
node-version: "24"
- run: pnpm install --frozen-lockfile
- run: pnpm run check
- run: pnpm run buildpnpm/action-setup must come before actions/setup-node (the pnpm cache needs the binary) and takes its version from packageManager. --frozen-lockfile fails the run when pnpm-lock.yaml is stale instead of rewriting it. Pin node-version to what Vercel's project settings use, and cache apps/web/.next/cache between runs if build time matters (the Next.js CI caching guide has the per-provider snippets).
Phase 8: Pre-launch checklist
Site URL and metadataBase
Create apps/web/lib/site.ts exporting siteUrl = "https://{{domain}}" and siteName, then set metadataBase: new URL(siteUrl) in the root layout's metadata export beside title: { default, template } and description. Every relative alternates.canonical and openGraph.images value resolves against it, and a relative value with no metadataBase is a build error. seo fills in the rest of that object after launch.
Search Console and Bing verification
Add the property for https://{{domain}} in Google Search Console and Bing Webmaster Tools before launch, and carry the tokens in metadata.verification (google, plus other: { 'msvalidate.01': '...' } for Bing) rather than a DNS record. Submit /sitemap.xml once the first deploy is live.
security.txt
Create apps/web/public/.well-known/security.txt with Contact:, Expires: (no more than a year out), Preferred-Languages:, and Canonical: https://{{domain}}/.well-known/security.txt. This is the one dotfile path that survives the static pipeline.
Favicon
Generate a favicon package from your source image at RealFaviconGenerator. Its Next.js export drops favicon.ico, icon0.svg, icon1.png, apple-icon.png, and manifest.json into apps/web/app/, and the web-app-manifest-192x192.png / web-app-manifest-512x512.png files into apps/web/public/. Next.js turns the app/ files into <link rel="icon"> and <link rel="apple-touch-icon"> tags through its file conventions; nothing goes in metadata.icons.
OG image
Create in apps/web/app/:
opengraph-image.png(1200x630, under 8 MB or the build fails)opengraph-image.alt.txt(one line of alt text; it becomesog:image:alt)
Next.js App Router serves the file as both the Open Graph and the Twitter card image via file-based metadata conventions. A separate twitter-image.png is redundant: twitter:image falls back to the OG image when the file is absent, and a byte-identical duplicate only doubles the payload. Alternatively, generate the card with code (opengraph-image.tsx and ImageResponse from next/og); seo covers that pattern and the metadata merge rules that decide whether a page keeps its card.
Skill handoffs
After deployment, run these skills in order:
seo: metadata, structured data, sitemap, robots, Core Web Vitalsui-designAudit mode: accessibility, typography surface checks, interaction quality, craft polishui-animation: motion easing, timing, gestures, and review rules
Validation checklist
After all phases, verify:
-
pnpm run devstarts from project root (turbo runs apps/web) and the dev overlay reports no instant-navigation insight on the home route -
pnpm run buildsucceeds with no errors, andpnpm --filter web startserves the production build (kill anything on port 3000 first;next starton a taken port fails silently while the old server keeps answering) -
pnpm run checkpasses lint, format, and type checks from the root -
pnpm exec lefthook run pre-commit --all-filespasses from the root - The root holds the only
pnpm-lock.yamlandpnpm-workspace.yaml: none inapps/web/, and nopackage-lock.jsonanywhere - The CI workflow ran green on the first pull request
-
apps/web/AGENTS.mdends with the Next-managednextjs-agent-rulesblock (written on the firstnext devfrom a coding agent) and is committed; any duplicateapps/web/CLAUDE.mdwrapper is removed; the Phase 5.1 design-system lint paragraph (includingultracite fix/ultracite fix --codex) is present outside those markers -
babel-plugin-react-compileris not inapps/web/package.json;ultracite(≥ 7.12),oxlint(≥ 1.80),oxfmt,lefthook, and@shadcn/lintare pinned, notlatest -
apps/web/oxlint.config.tsextendsultracite/oxlint/{core,next,react,shadcn}(framework order may vary) and hoistsjsPlugins: shadcn.jsPlugins. It does not use a hand-rolledjsPlugins: ["@shadcn/lint"]plus starter-onlyno-restyleblock.shadcn/no-restylestays off for**/components/ui/**via the preset (or a matching override whenaliases.uiis a different path) -
metadataBaseis set tohttps://{{domain}}andmetadata.verificationcarries the Search Console token -
git statusis clean afterpnpm run dev(no regenerated files left uncommitted) - GitHub repo has initial commit pushed
- Vercel deployment is live at
{{domain}} - Favicon appears in browser tab
- OG image renders in social card previews (use https://opengraph.xyz to test)