```html

Building a Multi-City Production Portal: From Monolith to Scalable Next.js 14 Architecture

Rick Drake Productions needed a modern, scalable platform to power production coordination across multiple cities. This post documents the technical decisions and infrastructure setup for converting a dated WordPress site into a dynamic Next.js 14 application that can rapidly scale to new markets.

The Problem: Legacy WordPress, Future Growth

The existing sandiegoproductions.com ran on WordPress with Visual Composer—a 2016-era stack that was difficult to maintain and couldn't handle the planned expansion to Phoenix, Palm Springs, and LA. Rick needed:

  • A hub domain (rickdrakeproductions.com) that unified the brand
  • City-specific pages or subdomains for San Diego, Las Vegas, and future markets
  • SEO-friendly URL structures for local search optimization
  • Rapid deployment of new city sites without duplication
  • A fallback referral business model if direct service expansion didn't materialize

Architecture Decision: Dynamic Routes over Static Subdomains

We chose dynamic route segments in Next.js 14 rather than separate subdomains or a traditional monorepo of separate apps. This decision was driven by several factors:

  • SEO Consolidation: Dynamic routes allow city pages to inherit domain authority from the hub, while still being discoverable via city-specific keywords
  • Maintenance: One codebase with parameterized city data beats maintaining separate domain configurations
  • Scalability: Adding Phoenix or Palm Springs requires only adding city data to a centralized config, not spinning up new Next.js instances
  • Analytics: Unified tracking across all cities in a single Google Analytics property

The alternative—subdomains like sandiego.rickdrakeproductions.com—would require separate SSL certificates, more complex DNS management, and scattered analytics. For a business with 5+ planned markets, dynamic routes won out.

Project Structure: Monorepo with pnpm Workspaces

We initialized a pnpm workspace to future-proof for additional apps (admin dashboard, API service, etc.):

rickdrakeproductions.com/
├── pnpm-workspace.yaml
├── package.json
└── apps/
    └── web/
        ├── next.config.ts
        ├── package.json
        ├── tsconfig.json
        ├── tailwind.config.ts
        └── src/
            ├── lib/
            │   ├── types.ts
            │   ├── cities.ts
            └── content.ts
            ├── app/
            │   ├── globals.css
            │   ├── layout.tsx
            │   ├── page.tsx (hub homepage)
            │   └── [city]/
            │       ├── layout.tsx
            │       ├── page.tsx
            │       ├── 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

The pnpm-workspace.yaml file declares the apps/web directory as a workspace member, allowing us to run pnpm install at the root and have dependencies installed in the correct isolation level.

City Data: Centralized, Typed Configuration

Rather than hardcoding city names into components, we created a single source of truth in apps/web/src/lib/cities.ts:

export const CITIES = [
  {
    slug: 'san-diego',
    name: 'San Diego',
    state: 'CA',
    phoneDisplay: '(619) 555-0123',
    serviceArea: 'San Diego County',
  },
  {
    slug: 'las-vegas',
    name: 'Las Vegas',
    state: 'NV',
    phoneDisplay: '(702) 555-0456',
    serviceArea: 'Clark County, NV',
  },
  // Future cities: phoenix, palm-springs, los-angeles
];

This approach means:

  • Adding Phoenix requires one new object in this array
  • Route validation happens automatically via TypeScript
  • We can fetch contact info from a CMS or database later without restructuring the app
  • Dynamic sitemaps, canonical tags, and hreflang attributes derive from this config

Dynamic Routes: The [city] Segment

Next.js 14's [city] dynamic segment in apps/web/src/app/[city]/page.tsx handles requests like /san-diego/ and /las-vegas/. The layout wrapper in apps/web/src/app/[city]/layout.tsx injects city-specific metadata, navigation breadcrumbs, and contact information.

Each nested route like apps/web/src/app/[city]/services/page.tsx receives the city param and can display city-specific fleet, pricing, or availability without code duplication.

Infrastructure: Route53 + CloudFront + ACM

Domain registration and DNS were handled entirely within AWS:

  • Route53 Domain Registration: rickdrakeproductions.com was registered via Route53 with privacy protection enabled. This avoids WHOIS scraping and keeps the domain locked within the AWS ecosystem.
  • CloudFront Distribution: A CloudFront distribution (ID: E2Q4UU71SRNTMB) sits in front of the Next.js app on Vercel or self-hosted infrastructure, providing edge caching, compression, and DDoS protection.
  • ACM Certificate: A wildcard certificate for *.rickdrakeproductions.com and www.rickdrakeproductions.com was issued via AWS Certificate Manager and attached to the CloudFront distribution.
  • DNS Aliases: Route53 aliases point both rickdrakeproductions.com and www.rickdrakeproductions.com to the CloudFront distribution.

Why this stack? CloudFront edge locations in 200+ cities mean city pages render with sub-100ms latency globally. If Rick expands to LA or Phoenix, clients in those regions get local-like performance out of the box.

Build & Deployment Challenges: lightningcss Native Binaries

Tailwind CSS 4 introduced a build-time dependency on lightningcss, which requires native binaries for different architectures. On macOS (Darwin x64), the build would fail until we explicitly installed:

npm install lightningcss-darwin-x64 --save-optional

This taught us a lesson about optional dependencies in monorepos: ensure platform-specific binaries are available before running next build. In CI/CD, we'll need conditional logic to install lightningcss-linux-x64