Skip to main content
War.re is organised as a monorepo containing two fully independent Next.js applications. Each app lives in its own directory, carries its own package.json and yarn.lock, and builds and deploys separately — there is no shared runtime, no shared node_modules, and no cross-app imports. You work inside whichever app directory you need and treat the other as a sibling project.

The Two Apps

DirectoryDomainPurpose
main/war.rePersonal homepage
subdomains/ryan/ryan.war.reOnline resume
Both apps use the Pages Router (pages/), not the App Router. Each has its own _app.tsx and _document.tsx for custom wrappers, its own styles/ directory for CSS modules, and its own public/ directory for static assets such as favicons and OG images.

Directory Tree

The repository root holds the two app directories alongside a small scripts/ folder and repo-level config files. Here is a condensed view of the key paths:
war.re/                          ← repository root
├── CLAUDE.md                    ← project-wide conventions for AI tools
├── README.md
├── scripts/                     ← shared utility scripts

├── main/                        ← Next.js app for war.re
│   ├── package.json
│   ├── yarn.lock
│   ├── next.config.js           ← output: 'export', reactStrictMode: true
│   ├── tsconfig.json
│   ├── pages/
│   │   ├── _app.tsx
│   │   ├── _document.tsx
│   │   ├── index.tsx            ← meta-refresh redirect → /n
│   │   ├── n/
│   │   │   └── index.tsx        ← homepage content (war.re/n)
│   │   └── 404.tsx
│   ├── styles/
│   ├── public/
│   └── e2e/                     ← Playwright smoke tests

└── subdomains/
    └── ryan/                    ← Next.js app for ryan.war.re
        ├── package.json
        ├── yarn.lock
        ├── next.config.js       ← output: 'export', reactStrictMode: true
        ├── tsconfig.json
        ├── pages/
        │   ├── _app.tsx
        │   ├── _document.tsx
        │   ├── index.tsx        ← meta-refresh redirect → /n
        │   ├── n/
        │   │   └── index.tsx    ← resume content (ryan.war.re/n)
        │   └── 404.tsx
        ├── components/          ← resume-specific React components
        │   ├── Activity.tsx
        │   ├── ActivityList.tsx
        │   ├── Experience.tsx
        │   └── ExperienceItem.tsx
        ├── styles/
        ├── public/
        ├── __tests__/           ← Jest + React Testing Library
        └── e2e/                 ← Playwright smoke tests

URL Routing and the /n Redirect

Neither app uses server-side rewrites. Both are configured with output: 'export' in next.config.js, which generates a fully static site that Vercel serves directly from the filesystem. The root path (/) on each domain uses an HTML <meta http-equiv="refresh"> tag to immediately redirect the browser to /n, which is where the actual content lives. This approach works without any server logic:
  • Visiting war.re → browser reads the meta-refresh → navigates to war.re/n
  • Visiting ryan.war.re → browser reads the meta-refresh → navigates to ryan.war.re/n
A <noscript> fallback link is included for browsers with JavaScript disabled so they still get a clickable link to /n.

Pages Directory Structure

Each app mirrors the same pages/ layout:
FileRouteRole
pages/index.tsx/Meta-refresh shell; redirects to /n
pages/n/index.tsx/nMain content page
pages/_app.tsxCustom App wrapper
pages/_document.tsxCustom Document wrapper
pages/404.tsx/404Custom not-found page
The subdomains/ryan/ app also has a components/ directory at the app root, holding the resume-specific React components (Activity, ActivityList, Experience, ExperienceItem) that the /n page composes together.
Both apps configure a TypeScript path alias @/* that maps to the app’s own project root. Use @/components/Foo instead of relative paths like ../../components/Foo to keep imports clean and refactor-safe as the directory tree grows.

Independent Builds

Run all commands from inside the relevant app directory — not from the repository root. Each app is self-contained:
# Work on the main site
cd main/
yarn dev          # dev server at localhost:3000
yarn build        # static export → out/
yarn lint         # ESLint
yarn typecheck    # tsc --noEmit
yarn format       # Prettier (format:check to verify without writing)
yarn e2e          # Playwright smoke tests (run yarn build first)

# Work on the resume
cd subdomains/ryan/
yarn dev          # dev server at localhost:3000
yarn build        # static export → out/
yarn test         # Jest + React Testing Library
yarn lint         # ESLint
yarn typecheck    # tsc --noEmit
yarn format       # Prettier (format:check to verify without writing)
yarn e2e          # Playwright smoke tests (run yarn build first)
The main/ app currently has no Jest unit tests (--passWithNoTests is set so the test script exits cleanly). The subdomains/ryan/ app has full Jest coverage for its components.