Reviewed-on: #11
portfolio
Personal portfolio site. Next.js App Router frontend, PocketBase as the CMS, rendered with on-demand ISR — content is served static and revalidated by webhook when the CMS changes.
Stack
- Next.js 16 (App Router, React 19, standalone output)
- TypeScript (strict)
- Tailwind CSS v4
- PocketBase — headless CMS, fetched at build/runtime via a typed client
- Biome — lint + format
- Vitest + Testing Library — unit/component tests
- Storybook (a11y + Vitest addons) — component workshop
- Yarn 4
Architecture follows Feature-Sliced Design
adapted to Next.js (src/{shared,entities,widgets} + app/).
Rendering
Pages are statically generated and served through ISR. There is no fixed TTL —
PocketBase calls the /api/revalidate webhook after any content mutation,
which purges the matching cache tag (revalidateTag). See
app/api/revalidate/route.ts.
Getting started
yarn install
yarn dev
Open http://localhost:3000.
Without a reachable PocketBase, the app falls back to fixtures in
app/api/collections/[collection]/records/route.ts.
Environment
PB_URL # internal PocketBase URL (server-side fetches)
PB_PUBLIC_URL # public PocketBase URL (image optimizer allowlist, build-time)
REVALIDATE_SECRET # shared secret for the /api/revalidate webhook
PB_PUBLIC_URL must be present at next build time — images.remotePatterns
is frozen into the build.
Scripts
| Command | Description |
|---|---|
yarn dev |
Dev server |
yarn build |
Production build (standalone) |
yarn start |
Serve the build |
yarn test |
Run tests (Vitest) |
yarn check |
Lint + format (Biome, writes fixes) |
yarn check:ci |
Lint + format check (no writes) |
yarn storybook |
Storybook on :6006 |
Deployment
Built as a standalone Docker image; PocketBase reached over the internal Docker network. Content updates trigger the revalidation webhook — no rebuild needed.