Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ coverage
__tests__
**/*.test.ts
**/*.spec.ts
vitest.config.ts
vitest.setup.ts

# Environment files (will be provided at runtime)
.env
Expand Down
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab
indent_size = 2

[*.{json,jsonc,yml,yaml,md,svg}]
indent_style = space
indent_size = 2
7 changes: 5 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Environment Variables
# Copy this file to .env

# Domain name for the application (used in metadata, SEO, sitemaps)
NEXT_PUBLIC_DOMAIN_NAME=acme.com
# Public site domain — used for canonical URLs, metadata, OpenGraph, sitemap,
# robots, and JSON-LD. NOTE: this is a NEXT_PUBLIC_* variable, so it is inlined
# into the client bundle at BUILD time. Changing it requires a rebuild.
# If unset, the app falls back to localhost:3000 (see lib/env.ts).
NEXT_PUBLIC_DOMAIN_NAME=example.com

# Port to expose the application on
PORT=3000
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm lint

- name: Typecheck
run: pnpm typecheck

- name: Test
run: pnpm test:run

- name: Build
run: pnpm build
env:
NEXT_PUBLIC_DOMAIN_NAME: ci.example.com
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.next
out
build
node_modules
pnpm-lock.yaml
public
*.md
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"useTabs": true,
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 80
}
62 changes: 29 additions & 33 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
pnpm dev # Start dev server
pnpm build # Production build (also runs TypeScript checking)
pnpm lint # ESLint
pnpm typecheck # tsc --noEmit
pnpm test:run # Vitest (run once); `pnpm test` for watch
pnpm format # Prettier
pnpm start # Start production server
```

Expand All @@ -22,50 +25,43 @@ make down # Stop containers
make logs # View logs
```

No test framework is configured.
Testing uses **Vitest** (`*.test.ts`, run with `pnpm test:run`). CI runs lint + typecheck + test + build (`.github/workflows/ci.yml`).

## Architecture

**Next.js 16 App Router** with React 19, Tailwind CSS v4, and shadcn/ui (base-nova style, Tabler icons).
**Next.js 16 App Router** with React 19, Tailwind CSS v4, and shadcn/ui (base-nova style on **@base-ui/react** — no Radix), Tabler icons, Jotai.

### Key patterns

- **Server Components by default.** Only add `"use client"` for interactive components (motion animations, event handlers, forms with state).
- **State Management**: Uses **Jotai** for atomic state. Atoms are defined in `lib/store/*.ts`. Use `useAtom`, `useAtomValue`, or `useSetAtom` in client components.
- **Async Dynamic APIs (Next.js 15/16)**: In server components (like `page.tsx` or `generateMetadata`), dynamic APIs like `params` and `searchParams` are Promises and must be awaited before accessing properties.
- **Layout wrapper**: `components/layout/Layout.tsx` wraps pages with `Header` + `Footer` + `CartSidebar`. Used in page components, not in `app/layout.tsx`.
- **Section components**: Page content is built from `components/sections/*.tsx` modules composed in page files.
- **SEO infrastructure**: `app/layout.tsx` has full metadata (OG, Twitter, robots) + JSON-LD via `components/JsonLd.tsx` using `schema-dts`. Sitemap auto-generates from filesystem in `app/sitemap.ts`.
- **Animations**: Use `motion/react`. Import as `import { motion } from "motion/react"`.
- **Styling**: Tailwind v4 with `@theme` block in `globals.css`. Color tokens use OKLCH. Use `cn()` from `lib/utils.ts` for class merging.
- **Toast notifications**: Sonner is integrated. `<Toaster />` in root layout. Use `import { toast } from "sonner"`.

### Environment
### Data, types & config layer (the foundation)

Requires `NEXT_PUBLIC_DOMAIN_NAME` (see `.env.example`). Used by metadata, robots.txt, and sitemap generation.
- **Single source of truth**: catalog data is in `data/products.ts` and `data/categories.ts`, validated by zod at module load. Don't inline product/category data in components.
- **Accessors**: read data through the async functions in `lib/data/products.ts` (`getAllProducts`, `getProductById`, `getProductsByCategory`, `getProductsOnSale`, `getFeaturedProducts`, `getSearchIndex`). Swap this module for a real backend.
- **Types**: `Product`/`Category` are derived from zod schemas in `lib/schemas/`. Import domain types from `@/types` (`Product`, `Category`, `CartItem`) — never re-declare them inline.
- **Money is integer cents** (`priceCents`). Format only at the view with `formatPrice` from `lib/format.ts`.
- **Brand/SEO/nav** live in `lib/config/site.ts` (`siteConfig`). Env is validated in `lib/env.ts` (`siteUrl`).

### Docker Configuration
### Key patterns

- **Dockerfile**: Multi-stage build optimized for Next.js standalone output
- **docker-compose.yml**: Production orchestration with health checks and resource limits
- **.dockerignore**: Optimized build context exclusions
- **output: "standalone"** in `next.config.ts` enables minimal production image (~150-200MB)
- Security: Non-root user (nextjs:1001), Alpine base, read-only where possible
- See `DOCKER.md` for comprehensive deployment guide
- **Server Components by default.** Add `"use client"` only on the smallest interactive leaf. Pattern: `components/products/ProductCard.tsx` is the client island; `ProductGrid` and the sections that compose it stay on the server. `motion/react` is used **only** in `app/not-found.tsx` — other entrance animations are CSS (`tw-animate-css`).
- **State (Jotai)**: atoms in `lib/store/cartStore.ts`. `addToCartAtom` is a pure data action (callers open the cart). Guard persisted reads with `useIsHydrated` (`lib/hooks/`).
- **Async Dynamic APIs**: `params`/`searchParams` are Promises — await before access.
- **Layout wrapper**: `components/layout/Layout.tsx` wraps pages with `Header` + `<main id="main-content">` + `Footer` + `CartSidebar`. The skip link and `ThemeProvider` live in `app/layout.tsx`.
- **SEO**: root metadata + WebSite/Organization JSON-LD in `app/layout.tsx`; per-page metadata via `pageMetadata` (`lib/seo.ts`); Product + BreadcrumbList JSON-LD on product pages. `app/products/[id]` is SSG via `generateStaticParams`; `app/sitemap.ts` includes product routes.
- **Styling**: Tailwind v4 `@theme` tokens (OKLCH) in `globals.css`, incl. `success`/`warning`/`rating`. Use `cn()`; avoid raw palette colors and `var()` in `className`. Dark mode via `next-themes` + the `.dark` token block; toggle in the Header.
- **Forms**: react-hook-form + zod (see `components/forms/ContactForm.tsx`).
- **Toasts**: Sonner; `<Toaster />` in root layout.

### Font setup
### Environment

Uses **Outfit** as the primary sans-serif font loaded via `next/font/google` in `app/layout.tsx`. Font display set to `swap`.
`NEXT_PUBLIC_DOMAIN_NAME` (see `.env.example`) — used for metadata, canonical URLs, sitemap, robots, JSON-LD. It is **build-time only** (inlined into the client bundle). Validated in `lib/env.ts`, which falls back to `localhost:3000`.

### Images
### Docker

Always use `next/image` with `fill` + `sizes` or explicit dimensions. Social sharing images in `/public`.
Two-stage Dockerfile (`builder` → `runner`) with `output: "standalone"`, non-root user, and a health check against `/api/health` (matched by `docker-compose.yml`). pnpm version comes from `package.json` `packageManager`. See `DOCKER.md`.

### Pages

- `/` - Home page with hero, categories, and featured products
- `/products` - Full product collection page
- `/products/[id]` - Dynamic product detail page (Async Params pattern)
- `/men`, `/women`, `/accessories`, `/sale` - Category collection pages
- `/contact`, `/shipping`, `/returns`, `/faq`, `/privacy`, `/terms` - Informational and customer service pages
- `/not-found` - Custom 404 page with animations
- `/` — hero, categories, featured products
- `/products` — full collection; `/products/[id]` — SSG product detail (Async Params)
- `/men`, `/women`, `/accessories` — category-filtered; `/sale` — on-sale products
- `/contact` (validated form), `/shipping`, `/returns`, `/faq`, `/privacy`, `/terms`
- `not-found.tsx` (animated 404, reduced-motion aware), `error.tsx` (route error boundary), `api/health`
45 changes: 45 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Contributing

Thanks for your interest in improving this template!

## Prerequisites

- **Node.js 24** (see `.nvmrc` — run `nvm use`)
- **pnpm 9+** (the repo pins a version via `packageManager`; `corepack enable` will respect it)

## Setup

```bash
pnpm install
cp .env.example .env # then set NEXT_PUBLIC_DOMAIN_NAME
pnpm dev
```

## Checks (run before opening a PR)

```bash
pnpm lint # ESLint
pnpm typecheck # tsc --noEmit
pnpm test:run # Vitest
pnpm build # production build (also type-checks)
```

CI (`.github/workflows/ci.yml`) runs the same steps on every push and PR.

## Conventions

- **Server Components by default.** Add `"use client"` only to the smallest leaf that needs interactivity (see `components/products/ProductCard.tsx` as the pattern).
- **Data lives in one place.** Product/category data is in `data/`; read it through the accessors in `lib/data/products.ts`. Don't inline catalog data in components.
- **Types come from the schema.** `Product`/`Category` are derived from the zod schemas in `lib/schemas/`; import domain types from `@/types`.
- **Money is integer cents.** Format for display with `formatPrice` from `lib/format.ts`.
- **Brand/SEO strings come from `lib/config/site.ts`.** Per-page metadata uses the `pageMetadata` helper in `lib/seo.ts`.
- **Components use named exports** and live under intent-based folders (`layout/`, `sections/`, `products/`, `cart/`, `forms/`, `providers/`, `ui/`).
- **Styling:** Tailwind v4 semantic tokens (`bg-primary`, `text-muted-foreground`, …) defined in `app/globals.css` — avoid raw palette colors and `var()` inside `className`. Use `cn()` for class merging.

## Adding shadcn/ui primitives

Only the primitives the template uses are committed. Add more on demand:

```bash
pnpm dlx shadcn add <name>
```
18 changes: 10 additions & 8 deletions DOCKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ NEXT_PUBLIC_DOMAIN_NAME=example.com
PORT=3000
```

**Important:** `NEXT_PUBLIC_DOMAIN_NAME` is required at build time for proper SEO and metadata generation.
**Important:** `NEXT_PUBLIC_DOMAIN_NAME` is a **build-time** value — Next.js inlines `NEXT_PUBLIC_*` variables into the client bundle during `pnpm build`. It is passed as a Docker **build arg** (see below). Changing it requires rebuilding the image, not just restarting the container; setting it only as a runtime `environment` value has no effect on already-built client code.

### Build Arguments

Expand All @@ -52,9 +52,10 @@ docker build --build-arg NEXT_PUBLIC_DOMAIN_NAME=yourdomain.com -t website-templ

**Stages:**

1. **deps**: Install production dependencies
2. **builder**: Build the Next.js application
3. **runner**: Minimal runtime with only necessary files
1. **builder**: Install dependencies and build the Next.js application
2. **runner**: Minimal runtime serving the standalone output

(The standalone output traces its own runtime `node_modules`, so no separate production-deps stage is needed.) The base image is `node:24-alpine`; the pnpm version is taken from `package.json` `"packageManager"` via corepack.

## Manual Docker Commands

Expand All @@ -69,21 +70,22 @@ docker build \

### Run Production Container

The domain is baked in at build time (above), so the run command does not need it:

```bash
docker run -d \
--name website-template \
-p 3000:3000 \
-e NEXT_PUBLIC_DOMAIN_NAME=example.com \
website-template:latest
```

## Health Checks

Production containers include health checks via `docker-compose.yml`:
Both the Dockerfile `HEALTHCHECK` and `docker-compose.yml` probe the app's health route:

- Checks every 30s
- 40s startup grace period
- Endpoint: `http://localhost:3000`
- Endpoint: `http://localhost:3000/api/health` (returns `{ "status": "ok" }`)

## Resource Limits

Expand Down Expand Up @@ -186,7 +188,7 @@ make prune

## CI/CD Integration

### GitHub Actions Example
A ready-to-use workflow at `.github/workflows/ci.yml` runs lint, typecheck, test, and build on every push/PR. To also build the Docker image, add a step:

```yaml
- name: Build Docker Image
Expand Down
Loading
Loading