Building a Multi-City Production Portal: Next.js 14 Monorepo with Dynamic Route Handling and CloudFront Distribution
This post documents the infrastructure and application architecture for Drake Productions' multi-city web platform, designed to serve individual city sites (San Diego, Las Vegas, Phoenix, Palm Springs, LA) from a single codebase with city-specific content and routing.
What Was Done
- Registered
rickdrakeproductions.comdomain via Route53 with privacy protection - Scaffolded a pnpm monorepo with
pnpm-workspace.yamlto support multiple applications - Built a Next.js 14 application with App Router in
apps/webusing TypeScript and Tailwind CSS 4 - Implemented dynamic city-based routing with catch-all segments and URL parameters
- Created type-safe city and content management system using TypeScript interfaces
- Configured CloudFront distribution with ACM certificate for HTTPS delivery
- Set up DNS architecture to support both hub domain and city-specific subdomains
Technical Architecture
Monorepo Structure & Dependency Management
The project uses pnpm with workspace configuration for scalability:
rickdrakeproductions.com/
├── pnpm-workspace.yaml # Defines workspace packages
├── package.json # Root configuration with shared dependencies
└── apps/
└── web/ # Next.js 14 application
├── next.config.ts
├── src/
│ ├── app/
│ │ ├── layout.tsx # Root layout with navigation
│ │ ├── page.tsx # Hub landing page
│ │ ├── globals.css # Global Tailwind styles
│ │ └── [city]/ # Dynamic city segment
│ │ ├── layout.tsx # City-specific layout
│ │ ├── page.tsx # City home page
│ │ ├── 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
└── package.json
The pnpm-workspace.yaml enables efficient dependency installation across packages, reducing duplication and improving build times.
Dynamic Routing with [city] Catch-All Segment
The core routing pattern uses Next.js 14's dynamic segments with the [city] directory:
// apps/web/src/app/[city]/page.tsx
import { notFound } from 'next/navigation'
import { getCityContent } from '@/lib/content'
import { CITIES } from '@/lib/cities'
export async function generateStaticParams() {
return CITIES.map(city => ({
city: city.slug
}))
}
export default function CityPage({ params }: { params: { city: string } }) {
const cityContent = getCityContent(params.city)
if (!cityContent) notFound()
return (
// City-specific content rendered here
)
}
This pattern allows /san-diego/, /las-vegas/, and future cities to be generated statically at build time via generateStaticParams().
Type-Safe Content Management
Centralized type definitions in apps/web/src/lib/types.ts
export interface City {
slug: string
name: string
state: string
description: string
}
export interface FleetVehicle {
id: string
name: string
capacity: number
citySlug: string
}
The apps/web/src/lib/cities.ts file exports the CITIES array, which drives both routing and navigation generation. Content queries are handled in apps/web/src/lib/content.ts, abstracting data fetching from components.
Infrastructure & Deployment
Domain Registration & DNS
The domain was registered via Route53 (AWS) rather than third-party registrars because:
- Unified Management: DNS records and domain registration are in the same AWS account
- Automation: No API handoff required between registrar and DNS provider
- Cost: Consistent pricing and no domain transfer fees
- Privacy: WHOIS privacy protection enabled at registration
Route53 hosted zone for rickdrakeproductions.com now accepts DNS records for:
rickdrakeproductions.com(A record pointing to CloudFront distribution)www.rickdrakeproductions.com(CNAME or alias to main domain)- City subdomains:
san-diego.rickdrakeproductions.com, etc. (future expansion)
CloudFront & HTTPS Delivery
CloudFront distribution E2Q4UU71SRNTMB was configured with:
- Origin: S3 bucket hosting Next.js static exports or EC2/container origin for dynamic rendering
- Certificate: ACM certificate covering
rickdrakeproductions.comandwww.rickdrakeproductions.com - Cache Behaviors: Static assets cached aggressively; dynamic pages with shorter TTLs
- Distribution Aliases: Added www subdomain to DNS validation and distribution configuration
Namecheap DNS records (from prior domain registrations) were evaluated for migration to Route53, but the primary delivery chain now flows through CloudFront with Route53 as authoritative DNS.
Build & Compilation Challenges
Tailwind CSS 4 & Native Dependencies
Tailwind CSS 4 requires lightningcss, which has optional native bindings for performance. On macOS, the build chain needed lightningcss-darwin-x64:
# Install optional native binary for macOS x64
npm install lightningcss-darwin-x64 --save-optional
# Verify installation
ls apps/web/node_modules/.bin/ | grep lightningcss
If the binary is missing, the build falls back to JavaScript implementation, which is slower but functional. This is relevant when running builds in CI/CD pipelines or containers with different architectures.
pnpm Installation Strategy
Given the large dependency tree (Next.js, React, TypeScript, Tailwind),