A static Next.js site providing practical guides, case studies, and tools for building communication systems that work when normal infrastructure fails.
Live at resilientcomms.org.
Built with Next.js 16 (output: 'export'), TypeScript, Tailwind CSS v3, Shadcn UI, MDX, and Lucide icons. Deployed to AWS S3 + CloudFront.
- Next.js 16 — App Router,
output: 'export'(fully static, no server runtime) - TypeScript — strict mode
- pnpm — sole package manager (
pnpm-lock.yamlis the lockfile) - Tailwind CSS v3 +
@tailwindcss/typography - Shadcn UI — component library (New York style, CSS variables,
components.json) - gray-matter — MDX files contain frontmatter only; content lives in React components
- lucide-react — SVG icon library used in
NavBar,CalloutBox, and inline footer links - Inter (Google Fonts) — sans-serif UI font (navigation, labels, tables)
- Geist Mono — local monospace font for code
pnpm install
pnpm dev # http://localhost:3000See CONTRIBUTING.md for PR workflow, lint expectations, and the code of conduct.
pnpm build # generates out/Output is written to out/. The build produces:
- Static HTML for all pages
out/sitemap.xml— all page URLs for search engine indexingout/robots.txt— crawl rules pointing to the sitemap
Automated (CI/CD): Merging to main triggers the Deploy workflow, which builds the site, syncs out/ to S3 with correct Cache-Control headers per file type, and invalidates the CloudFront distribution. No manual steps needed.
Manual: Copy .env.example to .env, fill in BUCKET_NAME, AWS_REGION, and CF_DISTRIBUTION_ID (and optional reference vars from the example), then:
source .env
bash scripts/deploy.shSee docs/deployment.md for the full setup guide (S3 bucket, CloudFront distribution, OAC, ACM certificate, Route 53 DNS) and instructions for configuring the required GitHub repository secrets (AWS_DEPLOY_ROLE_ARN, AWS_REGION, BUCKET_NAME, CF_DISTRIBUTION_ID).
content/
pages/ # One MDX file per top-level page (home, about, why-comms-fail, …)
case-studies/ # One MDX file per case study (v2)
playbooks/ # One MDX file per playbook (v2)
app/ # Next.js App Router pages and root layout
globals.css # Design tokens (CSS custom properties) and base styles
layout.tsx # Root layout: fonts, metadata, NavBar, footer, analytics slot
page.tsx # Homepage (loads content/pages/home.mdx)
sitemap.ts # Generates /sitemap.xml
robots.ts # Generates /robots.txt
about/ # /about route
brand/ # /brand route — design system reference page
case-studies/ # /case-studies route
playbooks/ # /playbooks route
resilience-stack/
resources/
technologies/
why-comms-fail/
components/ # Shared React components (NavBar, CalloutBox, PlaybookStep, etc.); re-exported from index.ts
ui/ # Shadcn UI components (added via `npx shadcn-ui@latest add <component>`)
lib/
content.ts # Content loading helpers for MDX frontmatter
glossary.ts # Glossary terms data and lookup
utils.ts # cn() utility — merges Tailwind classes via clsx + tailwind-merge
components.json # Shadcn UI configuration (style, paths, aliases)
public/ # Static assets
favicon.ico # Browser tab icon (placeholder — replace before launch)
apple-touch-icon.png # iOS home screen icon (placeholder — replace before launch)
og-image.png # Open Graph / Twitter card image (placeholder — replace before launch)
scripts/ # Deployment scripts (deploy.sh)
.env.example # Infrastructure env vars template (copy to .env)
CONTRIBUTING.md # Contribution guide
CODE_OF_CONDUCT.md
SECURITY.md # Vulnerability reporting policy
.github/
ISSUE_TEMPLATE/ # Bug report and feature request forms
PULL_REQUEST_TEMPLATE.md
workflows/
ci.yml # Lint + build on PRs and pushes to main
deploy.yml # Build → S3 sync → CloudFront invalidation on merge to main
docs/ # Architecture and deployment documentation
infra/ # Infrastructure notes (full IaC planned for v2)
out/ # Static export output (gitignored)
Global metadata is configured in app/layout.tsx using the Next.js Metadata API:
- Title template:
<Page Title> | Resilient Comms - Open Graph: type
website, title, description, and OG image (/og-image.png) - Twitter card:
summary_large_imagewith matching image - Favicon:
/favicon.ico+/apple-touch-icon.png
Each page exports its own metadata (or generateMetadata) derived from the MDX frontmatter in content/pages/. The ogImage frontmatter field sets a page-specific Open Graph image; if omitted, the global /og-image.png is used. See docs/content-model.md for the full frontmatter schema.
Before launch, replace these files in public/:
| File | Recommended size | Purpose |
|---|---|---|
og-image.png |
1200 × 630 px | Open Graph / Twitter card preview |
favicon.ico |
32 × 32 px | Browser tab icon |
apple-touch-icon.png |
180 × 180 px | iOS home screen icon |
A commented-out Google Analytics slot lives in app/layout.tsx. To enable it, uncomment the two <script> tags and replace GA_MEASUREMENT_ID with your actual measurement ID.
Design tokens and brand guidelines are defined in app/globals.css as CSS custom properties and surfaced to Tailwind via tailwind.config.ts. The design language is light-mode first — warm off-white backgrounds (#fdfcfb), near-black ink text (#0c0c0b), restrained dark-amber accents (#7a5c1e). Inspired by The Network State: literary, serif-forward, generous whitespace, minimal chrome.
Typography: Lora (serif) is the primary typeface for both headings and body prose. Inter is reserved for UI elements (navigation, labels, table cells). Links are near-black with underlines by default; on hover they become dark amber. Do not use blue for interactive elements.
Token categories:
| Category | Variables |
|---|---|
| Backgrounds | --background (#fdfcfb warm white), --background-subtle, --surface, --surface-raised, --surface-overlay |
| Text | --foreground (#0c0c0b near-black), --foreground-muted, --foreground-subtle, --foreground-inverse |
| Borders | --border (semi-transparent black), --border-strong, --border-subtle |
| Accent (interactive) | --accent (#7a5c1e dark amber), --accent-subtle, --accent-muted |
| Callout semantics | --callout-info-*, --callout-warn-*, --callout-tip-* |
| Typography | --font-sans, --font-serif, --font-mono, --text-*, --leading-*, --tracking-* |
| Spacing | --space-* |
| Border radius | --radius-* (minimal values, 2px–8px) |
| Shadows | --shadow-* |
| Transitions | --transition-* |
| Layout | --max-width-prose (68ch), --max-width-content (76rem), --max-width-narrow (52rem) |
All Tailwind color, font, spacing, shadow, and radius utilities are wired to these CSS variables. Always use semantic tokens in components — never hardcode primitive hex values. Use the prose class from @tailwindcss/typography for MDX body content (do not use prose-invert).
The living brand guidelines page at /brand documents the full color palette, type scale, and component patterns.
Shadcn UI is configured via components.json (New York style, CSS variables enabled). Components are added on demand using the CLI and land in components/ui/.
npx shadcn-ui@latest add buttonThe cn() utility in lib/utils.ts merges Tailwind classes with conflict resolution:
import { cn } from "@/lib/utils";Shadcn HSL color tokens (--card, --primary, --muted, --destructive, etc.) are defined alongside the project's own design tokens in app/globals.css and mapped into Tailwind via tailwind.config.ts. Do not rename or remove these variables — they are required by every added Shadcn component.
Pages are written in MDX and live in content/pages/. Each file requires YAML frontmatter with at minimum title and description. The ogImage field is optional but recommended.
content/pages/
home.mdx # / (homepage)
why-comms-fail.mdx # /why-comms-fail
resilience-stack.mdx
technologies.mdx
case-studies.mdx # /case-studies
playbooks.mdx # /playbooks
resources.mdx
about.mdx
brand.mdx # /brand — design system reference
MDX files can use custom components registered in components/index.ts:
<CalloutBox variant="tip|info|warning" title="…">— styled aside for tips, warnings, and notes
The shared layout components (not available in MDX) are:
<NavBar>— site-wide navigation header; rendered inapp/layout.tsx
See docs/content-model.md for the complete frontmatter schema and component reference.
app/sitemap.ts exports a Next.js sitemap() function that lists all top-level routes. The build outputs this as out/sitemap.xml. Add new routes to the routes array in that file whenever a new top-level page is added.
pnpm run lintdocs/content-model.md— frontmatter schema for all content types and MDX component referencedocs/deployment.md— step-by-step AWS setup and deploy instructionsinfra/README.md— infrastructure approach and v2 IaC plan/brand(live page) — design token reference and brand guidelines