Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nextjs-template

A personal Next.js starter:

  • App Router
  • TypeScript
  • app / ui / features / domain / lib layers
  • StyleX (Babel + PostCSS)
  • Tokens Studio JSON → StyleX vars
  • Storybook
  • Motion (motion/react) for layout and drag
  • next-intl (en / ru / uk / pt-BR)
  • TanStack Query
  • browser MSW
  • pnpm
  • ESLint / Prettier
  • SVGR (Turbopack)

Create an app from this template

npx create-next-app@latest my-app --example https://github.com/4erkashin/nextjs-template --use-pnpm

Use --use-pnpm so the installer matches this lockfile. Or use Use this template on GitHub.

Run locally

Node 24 (engines.node in package.json). fnm is one way to get it:

fnm install 24
fnm use 24

Install pnpm with the standalone script or npm install -g pnpm. The lockfile pin is packageManager in package.json.

pnpm install
pnpm dev        # http://localhost:3000
pnpm storybook  # http://localhost:6006

No .env* file is required. STORYBOOK_URL is optional (see .env.example). When it is set, the home page links that address. When it is unset, the same sentence is plain text. pnpm storybook starts Storybook and reuses a Next.js server already responding on port 3000, or starts one if needed. The App/OG images story shows the generated image routes for all four locales in one pane.

The Next.js app and Storybook are separate Vercel projects. In the app project, set STORYBOOK_URL to the Storybook production origin and SITE_URL to the app's production origin. SITE_URL feeds Next.js metadataBase; preview deployments use their Vercel deployment URL, and local builds default to http://localhost:3000. In the Storybook project, set STORYBOOK_OG_BASE_URL to the stable production app origin so the deployed Storybook story loads its images from the deployed app. The story reads that value at build time; without it, image URLs default to http://localhost:3000.

Releases

PRs run CI but do not create Vercel deployments. Git pushes do not deploy either project. To release several merged PRs together, tag the chosen main commit with a v.* tag (for example, v.0.0.1) and push the tag. The release workflow checks that commit, deploys Storybook, waits for it to succeed, then deploys the app. After both deployments succeed, it creates a GitHub Release for the tag with automatically generated notes. Both deployments appear in GitHub's production environment with their Vercel URLs. A failed check or deployment prevents later release steps from running.

The workflow uses the GitHub Actions secret VERCEL_TOKEN, with access to both Vercel projects. Rotate it before it expires.

dev, storybook, typecheck, and build run tokens:build first. While dev or Storybook is running, edits to tokens/tokens.json rebuild generated files. Do not edit tokens/generated/.

Internationalization (next-intl)

Locales live in i18n/routing.ts: English is unprefixed (/), the others are /ru, /uk, /pt-BR. Copy sits in messages/{locale}.json beside the page, feature, or UI component that owns it. i18n/catalogs/ merges those files with explicit imports. The app loads only the requested locale; Storybook loads every locale. TypeScript keys and ICU arguments are typed from the aggregated English catalog.

  • Import Link / useRouter / usePathname / redirect / permanentRedirect from @/i18n/navigation, not next/link or next/navigation. notFound, useParams, and useSearchParams stay on next/navigation.
  • First visit negotiates Accept-Language (then a cookie). Unknown languages fall back to en.
  • Storybook has a locale toolbar (storybook-next-intl).

StyleX and tokens

tokens/tokens.json → gitignored tokens/generated/. Do not edit generated files. dev, typecheck, storybook, and build generate first. dev and storybook then watch the token sources and rebuild.

  • Themes: light / dark / system from @/tokens/generated/themes.
  • Motion tweens: tokens/generated/motion.ts. Reduced motion is MotionConfig.
  • stylex.create; nest conditions on the property.
  • CSS motion: generated motion vars + queries.reducedMotion. Cookbook: ui/cookbook-stylex/.
  • motion/react: layout / drag / sequence, not hover color. Cookbook: ui/cookbook-motion/.
  • Next: Babel + PostCSS. Storybook: @stylexjs/unplugin + addon-themes (toolbar ≠ cookie).
  • Reset: modern-normalize, then a preflight layer: UA block margins off; headings inherit size and weight; links inherit color and decoration.

Typography

Sans is Onest in theme/fonts.ts, a variable face: CSS var --font-sans, token fonts.sans. It is the document default (<html>). Mono is JetBrains Mono the same way (--font-mono, fonts.mono). Where the code already sets fonts.mono, it stays mono. fontWeight on those variable faces are real cuts, not fakes.

Client data (Query + MSW)

pnpm dev starts a browser Mock Service Worker. Add handlers for endpoints that do not exist yet; everything else hits the real network. Handlers live in mocks/handlers.ts (empty until you add some).

  • Kill switch: NEXT_PUBLIC_MSW=0 (see .env.example).
  • Remove MSW: delete mocks/, drop MswGate from app/providers.tsx. Full sweep: mocks/README.md.
  • Query (lib/query/) stays when MSW goes.

Dependency updates

Direct deps are exact versions (no ^, no latest). packageManager pins pnpm. Renovate opens grouped PRs weekly; nothing auto-merges.

This repo cannot install the GitHub App or flip Dependabot alerts for you. After the files are on main:

./scripts/enable-dep-loop.sh

Git hooks

commit-msg, pre-commit, and pre-push run through lefthook. Do not pass --no-verify to dodge them.

Commit messages

Every commit is type: subject. The first line is a type (what kind of change), a colon and space, then a short subject (what changed). Allowed types are listed in conventional-commits.json; the commit-msg hook rejects anything else.

feat: add password reset

Scope is optional. Put the area of the code in parentheses between the type and the colon when that helps — a package, route, or layer, not a ticket number.

fix(api): handle empty payload

! is optional. Put it immediately before the colon when the change is breaking (callers or users must change how they use this). That is what would become a major version if this repo ever published a package.

feat!: drop v1 routes
feat(api)!: require auth on /export

feat is a new capability, fix is a bug fix, ! is breaking. Other types (docs, chore, refactor, …) are for humans and tools; they do not imply a version bump.

Prefixes listed as exceptions in that file skip the type check.

pre-commit

Prettier formats staged files and may restage them. ESLint then autofixes staged JS/TS and restages those fixes. The commit is refused when an ESLint error remains after that autofix, when a staged file contains git conflict markers, or when you stage .env / .env.* (.env.example is allowed). If you stage token JSON or tokens/build.js, pnpm tokens:build runs.

pre-push

pnpm typecheck runs when the push includes TypeScript, tsconfig*.json, package.json, pnpm-lock.yaml, next.config.*, token sources, babel.config.js, or postcss.config.*. Docs-only pushes skip it.

Bundle analysis

This app builds with Turbopack. When a client import looks heavy, inspect the production graph (Next 16.1+):

pnpm next experimental-analyze

--output writes .next/diagnostics/analyze for before/after diffs. The command is experimental; there is no analyze script so clones do not inherit a frozen CLI name. Storybook is Vite — this UI does not cover it.

Not shipping

Tools that look useful and are still out. create-next-app --example copies every script and CI job; “strip it later” is only true for this git history.

Knip

Knip finds unused files, unused exports, and leftover package.json dependencies. ESLint only sees unused locals inside a file.

It auto-detects Next app/**/page and Storybook *.stories.*. It does not understand tokens/build.js, gitignored StyleX under tokens/generated/, scripts/enable-dep-loop.sh, the MSW worker in public/, or empty features/ / domain/ placeholders. That ignore/entry list would become starter contract: every clone pays it, and this repo would have to keep it honest.

Unused locals stay an ESLint warning. Unused packages stay a Renovate/review problem. Run npx knip ad hoc if you want a one-shot report; do not add the dependency.

Cycle detection

File cycles (a.ts → b.ts → a.ts) can yield undefined at module init. This starter does not fail CI on them.

import/no-cycle skips type-only imports (this repo uses those on purpose) and gets expensive as clones grow. madge / dependency-cruiser need the same ignore/entry list as Knip: generated tokens, Storybook, scripts, mixed @/ and relative StyleX paths. Clones inherit that list.

Layer direction is already ESLint: ui/ must not import features/ or domain/. Barrels (index.ts as a public API) are allowed; do not add a scanner to police them.

Run npx madge --circular --extensions ts,tsx --ts-config tsconfig.json app ui lib theme i18n mocks features domain ad hoc if you want a one-shot report. Do not add the dependency.

@next/bundle-analyzer

The Webpack plugin (ANALYZE=true next build). Extra dependency, wraps next.config, and does not replace the Turbopack analyzer above. Do not add it.

Bundle-size budget

No kilobyte cap in CI. The dispatched CI workflow runs pnpm build and pnpm build-storybook. An absolute first-load cap is wrong the day a clone adds a feature; a percent-vs-main check needs a stored baseline and a production build on every pull request. Use Bundle analysis when something feels wrong. Do not add size-limit, bundlesize, or a first-load JS gate.

About

Personal Next.js starter: App Router, TypeScript, StyleX, pnpm, ESLint/Prettier, SVGR

Topics

Resources

Code of conduct

Contributing

Stars

7 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages