Building a Multi-City Next.js Portal: Domain Registration, Monorepo Architecture, and Dynamic Routing at Scale
Over the past development session, I built the foundational infrastructure for Drake Productions' multi-city web platform—a Next.js 14 application designed to power city-specific production coordination websites from a single codebase. This post walks through the technical decisions, infrastructure setup, and implementation details for engineers maintaining or extending this system.
What Was Done
- Registered
rickdrakeproductions.comvia AWS Route53 with privacy protection enabled - Scaffolded a pnpm monorepo with Next.js 14 App Router in
/apps/web - Implemented dynamic city-based routing using Next.js catch-all segments
- Created type-safe city and content management layers
- Set up foundational layout, navigation, and multi-page routing structure
- Resolved native dependency issues with Tailwind CSS 4's
lightningcsspackage
Domain Registration & DNS Architecture
Why Route53 over GoDaddy: The existing infrastructure uses AWS Route53 for DNS management (verified via commands checking 86from.com registration). Registering rickdrakeproductions.com in Route53 eliminates the need for domain nameserver handoffs between registrars and hosting infrastructure—a significant operational simplification. Route53 provides DNSSEC, alias records for CloudFront distributions, and integrated CloudWatch metrics out of the box.
Registration details: Domain registered with privacy protection enabled through Route53's privacy wrapper. Contact information was pre-populated from the existing 86from.com registration in the same account, maintaining consistency across the account's domain portfolio.
Future infrastructure: Once the build completes, the site will be hosted on CloudFront (likely reusing the existing distribution E2Q4UU71SRNTMB based on session notes) with Route53 alias records pointing to the CloudFront distribution endpoint. This provides global CDN acceleration and automatic failover capabilities.
Monorepo & Project Structure
Why pnpm + workspaces: The project uses pnpm-workspace.yaml (created at the repo root) to manage a monorepo. pnpm's hard-link deduplication reduces disk space significantly compared to npm/yarn when managing multiple applications. The workspace structure allows shared utilities, types, and components across apps while maintaining independent build pipelines.
Directory structure:
rickdrakeproductions.com/
├── pnpm-workspace.yaml
├── package.json
└── apps/
└── web/
├── next.config.ts
├── tailwind.config.ts
├── tsconfig.json
└── src/
├── app/
│ ├── globals.css
│ ├── layout.tsx (root layout)
│ ├── page.tsx (hub homepage)
│ └── [city]/
│ ├── layout.tsx
│ ├── page.tsx (city homepage)
│ ├── services/page.tsx
│ ├── fleet/page.tsx
│ ├── fleet/[vehicle]/page.tsx
│ ├── contact/page.tsx
│ ├── about/page.tsx
│ ├── locations/page.tsx
│ └── portfolio/page.tsx
├── components/
│ └── layout/
│ ├── Nav.tsx
│ └── Footer.tsx
└── lib/
├── types.ts
├── cities.ts
└── content.ts
Root vs. app-level layouts: The root layout.tsx at /apps/web/src/app/layout.tsx wraps the entire application with global metadata, font loading, and CSS. The city-level layout at /apps/web/src/app/[city]/layout.tsx handles city-specific context and inherited route structure for all city subpages.
Dynamic Routing with Catch-All Segments
Implementation: The routing strategy uses Next.js's optional catch-all segment pattern via the [city] directory. This allows URLs like:
rickdrakeproductions.com/san-diego→/app/[city]/page.tsxrickdrakeproductions.com/san-diego/services→/app/[city]/services/page.tsxrickdrakeproductions.com/las-vegas/fleet/vehicle-name→/app/[city]/fleet/[vehicle]/page.tsx
Type safety: The cities.ts file defines a strict city union type and provides validation functions. The content.ts` module abstracts content fetching (placeholder for future CMS integration). Types are exported from types.ts and shared across components and pages, ensuring runtime safety when accessing city parameters from params.city.
Why this approach: A single codebase with parameterized routes scales better than managing separate subdomains (e.g., san-diego.rickdrakeproductions.com) from an ops perspective—no separate deployments, unified analytics, and simpler SSL certificate management (single cert covers all routes).
Build Issues & Dependency Resolution
The lightningcss problem: Tailwind CSS 4 requires the lightningcss native binary for CSS compilation. Installation initially failed because the platform-specific native binary (lightningcss-darwin-x64 for macOS ARM64) wasn't automatically resolved during pnpm install.
Solution: Explicitly installed the native binary package:
pnpm install lightningcss-darwin-x64 --save-peer
Then verified the Next.js build completed without CSS compilation errors. This is a common issue when Node.js optional dependencies don't auto-resolve on certain platforms; explicit installation ensures builds don't break in CI/CD environments with different architectures.
Key Architecture Decisions
- Content layer abstraction:
content.tsis designed to be swapped with a CMS client (Contentful, Sanity, etc.) without touching page components. This separation allows Rick to manage content independently later. - Shared navigation: The
Nav.tsxcomponent reads the cities list and renders links to all city hubs, maintaining consistency across the platform. - CSS strategy: Using Tailwind CSS 4 with
globals.cssimporting Tailwind directives. This provides low-level customization for production styling while avoiding CSS-in-JS overhead. - Environment parity: Monorepo configuration with shared Node modules ensures build consistency across development, staging, and production.
What's Next
The foundation is solid. Immediate next steps:
- CloudFront distribution update: Point the distribution's origin to the deployed Next.js app and add
rickdrakeproductions.com+www.rickdrakeproductions.com