Next.js 16 starter template by darkroom.engineering.
Stack: Next.js 16, React 19, TypeScript (strict), Tailwind v4 + CSS Modules, Bun, Biome, React Compiler ON.
| Document | Purpose |
|---|---|
ARCHITECTURE.md |
Architectural decisions and patterns |
BOUNDARIES.md |
What to customize vs what is framework |
components/README.md |
Component inventory and conventions |
lib/README.md |
Library structure overview |
lib/integrations/*/README.md |
Per-integration docs (Sanity, Shopify, HubSpot) |
lib/styles/README.md |
Design system and style generation |
components/effects/README.md |
Animation component docs |
These break the build or cause bugs if violated.
import { Image } from '@/components/ui/image' // NOT next/image
import { Link } from '@/components/ui/link' // NOT next/linkBiome plugin no-anchor-element enforces no raw <a> tags. Biome rule noImgElement: error enforces no raw <img> tags.
import s from './component.module.css'import { Image } from '@/components/ui/image'
import { useRect } from '@/hooks/use-rect'
import { clamp } from '@/utils/math'Available aliases: @/*, @/components/*, @/lib/*, @/hooks/*, @/styles/*, @/integrations/*, @/webgl/*, @/utils/*, @/config, @/dev/*.
Biome plugin no-relative-parent-imports enforces this -- no ../ imports.
Only add 'use client' when you need hooks, event handlers, or browser APIs. Keep data fetching in Server Components and pass props down.
React Compiler handles all optimization. Never use useMemo, useCallback, or React.memo.
Exception: Use useRef for class/object instantiation to prevent infinite loops:
const instanceRef = useRef<SomeClass | null>(null)
if (!instanceRef.current) {
instanceRef.current = new SomeClass(params)
}noExplicitAny: error in Biome. Use unknown + type narrowing instead.
useSortedClasses: error in Biome. Classes in className, class, cn(), and clsx() must be sorted.
verbatimModuleSyntax: true in tsconfig. useImportType: error and useExportType: error in Biome.
import type { ComponentProps } from 'react'Dispose materials, textures, geometries, and render targets on unmount. Remove event listeners. Gate debug UI with process.env.NODE_ENV === 'development'.
All integrations (Sanity, Shopify, HubSpot) must gracefully handle missing env vars. Use fetchWithTimeout for external API calls.
proxy.ts # Next.js 16 request proxy (rate limiting, auth)
app/ # Routes only -- no components here
components/
ui/ # Reusable primitives (Image, Link, Form, etc.)
layout/ # Page chrome (Wrapper, Header, Footer, Theme, Lenis)
effects/ # Animation components (GSAP, SplitText, etc.)
lib/
env.ts # Typed environment variables (Zod-validated singleton)
hooks/ # Custom hooks + Zustand stores
utils/ # Pure utilities (math, fetch, metadata, strings, animation, validation)
styles/ # Design system, Tailwind config (CSS-based for v4)
integrations/ # Third-party services + registry (Sanity, Shopify, HubSpot)
webgl/ # 3D graphics (optional, lazy-loaded)
dev/ # Debug tools (stripped in production)
features/ # Root layout conditional features
scripts/ # CLI tools (dev, setup)
strict: trueplus:noImplicitOverride,exactOptionalPropertyTypes,useUnknownInCatchVariables,noFallthroughCasesInSwitch,noImplicitReturns,noUnusedLocals,noUnusedParameters,noUncheckedIndexedAccess,noUncheckedSideEffectImports- Target: ES2023, module: ESNext, moduleResolution: bundler
verbatimModuleSyntax: true-- useimport typefor type-only imports- Prefer
interfacefor object shapes,typefor unions/intersections - Props extend
ComponentProps<'element'>when wrapping HTML elements - React 19:
refis a regular prop, noforwardRefneeded
- Tailwind v4: CSS-based config (no
tailwind.config.js), uses@themedirective - CSS Modules: For complex animations, custom layouts, CSS specificity
- Combine with
cn()fromclsx - Design tokens in
lib/styles/css/root.css - Custom viewport functions:
mobile-vw(),mobile-vh(),desktop-vw(),desktop-vh() - Column function:
columns(n)for grid-based sizing - Custom
dr-*utility classes for responsive scaling (seelib/styles/README.md) - Use
h-dvhnoth-screen - Animate only
transform,opacity(compositor properties) - Desktop breakpoint: 800px
// Standard component pattern
import s from './my-component.module.css'
import cn from 'clsx'
import type { ComponentProps } from 'react'
interface MyComponentProps extends ComponentProps<'div'> {
variant?: 'primary' | 'secondary'
}
export function MyComponent({ variant = 'primary', className, ...props }: MyComponentProps) {
return <div className={cn(s.root, className)} {...props} />
}- Named function declarations, not arrow functions
- Kebab-case filenames (
my-component.tsx,my-component.module.css) - CamelCase CSS class names (
.isPrimary,.isDisabled) - Zustand for global state, React state for component state
next/dynamicfor heavy components with{ ssr: false }when needed
| Package | Purpose |
|---|---|
lenis |
Smooth scroll (configured in layout) |
gsap |
Complex animations |
tempus |
RAF management |
hamo |
Performance hooks (useRect, etc.) |
@base-ui/react |
Unstyled UI primitives |
zustand |
Global state management |
clsx |
Class name composition (aliased as cn) |
zod |
Schema validation (env vars, forms, server actions) |
All server actions and form inputs use Zod schemas for validation. Env var checking uses Zod schemas via the integration registry.
// Server action validation
import { emailSchema, parseFormData } from '@/utils/validation'
const schema = z.object({ email: emailSchema, name: z.string().min(1) })
const result = parseFormData(schema, formData)
if (!('success' in result)) return result // Returns FormState on error
// Typed environment access
import { env } from '@/lib/env'
const projectId = env.NEXT_PUBLIC_SANITY_PROJECT_ID
// Integration registry (single source of truth)
import { isConfigured } from '@/integrations/registry'
if (isConfigured('sanity')) { /* ... */ }Client-side form validation uses the same Zod schemas via zodToValidator() bridge.
bun dev # Dev server with Turbopack
bun run build # Production build (runs setup:styles first)
bun run check # biome check + tsgo --noEmit + bun test
bun lint # Biome lint
bun lint:fix # Biome lint with auto-fix
bun run format # Biome format
bun run typecheck # tsgo --noEmit
bun test # Unit tests
bun run doctor # Diagnose setup issues (env validation included)Runs in parallel on staged files:
- Biome:
biome check --write --unsafeon*.{js,mjs,ts,jsx,tsx,css,scss} - Typecheck:
tsgo --noEmiton*.{ts,tsx}
- Conventional commits:
feat:,fix:,refactor:,docs:,chore: - No force push to
main - No
--no-verifyunless explicitly requested - Small, atomic commits
bun run check # Must pass: biome + types + testsFrom BOUNDARIES.md: modify pages and content freely, extend starter components by creating new ones alongside existing ones. Do not modify core ui/ primitives unless necessary -- create wrappers instead.