Building a Multi-City Next.js Portal: Architecture, Monorepo Setup, and Dynamic Routing for Drake Productions
This post documents the technical implementation of rickdrakeproductions.com, a multi-city portal designed to serve production coordination clients across San Diego, Las Vegas, Phoenix, Palm Springs, and LA. The challenge: build a scalable, SEO-friendly architecture that shares common infrastructure while allowing city-specific customization.
What Was Built
We scaffolded a pnpm monorepo with a Next.js 14 application using the App Router, TypeScript, and Tailwind CSS 4. The core architecture supports dynamic city-based routing with the pattern /[city]/, enabling pages like /san-diego/services, /las-vegas/fleet, and /san-diego/portfolio to coexist within a single Next.js deployment.
Domain registration for rickdrakeproductions.com was completed via AWS Route53 with privacy protection enabled, consolidating DNS management with existing infrastructure already hosted in AWS.
Project Structure and Monorepo Setup
The monorepo layout reflects a scalable approach:
rickdrakeproductions.com/
├── pnpm-workspace.yaml
├── package.json
└── apps/
└── web/
├── next.config.ts
├── package.json
├── src/
│ ├── app/
│ │ ├── layout.tsx (root layout)
│ │ ├── page.tsx (homepage)
│ │ ├── globals.css
│ │ └── [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
│ └── lib/
│ ├── types.ts
│ ├── cities.ts
│ └── content.ts
The pnpm-workspace.yaml configuration declares the apps/web workspace, allowing shared dependencies at the root while maintaining app-specific configurations. This is crucial for scaling to additional apps later (admin dashboard, API service, etc.).
Dynamic City Routing and Type Safety
The routing strategy leverages Next.js 14's dynamic segments. /apps/web/src/app/[city]/layout.tsx wraps all city-specific pages, while /apps/web/src/app/[city]/page.tsx renders the city landing page.
City data is centralized in /apps/web/src/lib/cities.ts, which exports a list of supported cities:
export const CITIES = ['san-diego', 'las-vegas', 'phoenix', 'palm-springs', 'la'] as const;
export type City = (typeof CITIES)[number];
This pattern enables type-safe references throughout the codebase and powers static generation for known cities. The generateStaticParams() function in route handlers ensures all city pages are pre-rendered at build time, eliminating runtime overhead.
Content is abstracted in /apps/web/src/lib/content.ts, allowing city-specific strings, imagery, and service offerings to be managed separately from the component layer. This separation is essential for maintaining consistency across five cities while enabling localized updates without code changes.
Tailwind CSS 4 and Native Binary Dependencies
The project uses Tailwind CSS 4, which requires the lightningcss native binary for optimal build performance. During setup, the installation of this optional dependency failed due to platform-specific binary availability.
The solution involved explicitly installing the Darwin x64 variant:
npm install lightningcss-darwin-x64 --save-optional
This dependency is declared as optional in package.json, allowing the build to proceed even if the native binary is unavailable while leveraging it when present. The next.config.ts includes configuration to support Tailwind CSS 4's newer build pipeline:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
reactCompiler: false,
},
};
export default nextConfig;
Infrastructure and Deployment Strategy
Domain Registration: rickdrakeproductions.com was registered through AWS Route53. Route53 was chosen over external registrars because DNS, health checks, and routing policies are already centralized there, reducing operational complexity.
Build and Hosting: The Next.js application is configured for static generation where possible. The next.config.ts` file contains the build configuration. Pre-rendering city pages at build time ensures fast Time to First Byte (TTFB) and reduces server load. Dynamic routes like /[city]/fleet/[vehicle]/ leverage ISR (Incremental Static Regeneration) to balance freshness with performance.
DNS and SSL: Route53 hosts the authoritative DNS for rickdrakeproductions.com with privacy protection enabled. Future CloudFront distribution aliases will route traffic through AWS's CDN for global performance and native HTTPS termination.
Build and Dependency Installation
Dependency installation proved challenging in the development environment due to network constraints and large package graphs. The solution involved:
- pnpm installation: Installed globally via npm for faster, more reliable monorepo dependency resolution compared to npm or yarn.
- Timeout configuration: Adjusted pnpm's network timeout to accommodate slower connections during initial install.
- Selective installations: Installed packages excluding optional dependencies initially (skipping sharp and lightningcss binaries), then added platform-specific variants after verifying Node.js and environment compatibility.
- Next.js verification: Confirmed Next.js 14 was correctly installed and the TypeScript type definitions were accessible in
node_modules/next/dist/types.
A production build was executed to validate the entire pipeline:
cd apps/web && npm run build
This command confirmed that all city pages pre-render correctly, Tailwind CSS 4 compilation succeeds, and TypeScript compilation is error-free.
Key Architectural Decisions
- Monorepo over multi-repo: Shared components, utilities, and types are easily versioned together. Future services (admin panel, API) integrate cleanly.
- pnpm over npm: Faster installs, better disk space efficiency, and stricter dependency management prevent hidden transitive dependency issues.
- App Router over Pages Router: Nested layouts, server components, and streaming support provide better performance and developer experience for a multi-city portal.
- Static generation with ISR: City landing pages and service pages are pre-rendered. Fleet and vehicle details leverage ISR to balance freshness with performance, revalidating on demand rather than on every request.
- Route53 for domain