Next.js Multi-Site Frontend
One codebase, one deployment, four brands. These are SEO-dependent businesses today — that constraint shapes almost every decision below more than any technical preference does.
Multi-tenancy: domain-based, single dynamic build
Next.js middleware inspects the request hostname, resolves it against
sites.domain, and injects the resolved site (+ brand_config) into request
context for every Server Component down the tree. One build, one deployment,
serves heyaustin.com, laketravis.com, heycrestedbutte.com, 6street.com
simultaneously (all pointed at the same Cloudflare Worker via custom domains,
deployed with the @opennextjs/cloudflare adapter — see api-architecture.md
for the full hosting decision).
Rejected alternative: separate static export/build per site. Would isolate brands more strongly, but 4x's build/deploy/monitoring overhead for a small team — not worth it unless a brand needs genuinely divergent page structure, not just theming.
Theming
brand_config (jsonb, already in sites) drives CSS custom properties
(colors, fonts, logo/asset URLs) set at the root layout per request based on
the resolved site. Shared component library, per-site visual identity —
same pattern as the data model, applied to presentation.
Rendering strategy
- ISR (Incremental Static Regeneration) for venue pages, event pages, blog posts, category/region browse pages — static speed, but revalidated on-demand via a webhook the admin portal calls on publish/edit, not on a timer. A curator hitting "approve" in the ingestion queue should make the event page live within seconds, not wait for the next revalidation window.
- Server Components throughout for data fetching — no client-side waterfall for content that search engines and first paint both need.
- This alone should meaningfully beat the current WordPress setup's Core Web Vitals, which today has no full-page caching layer and a plugin-heavy render path.
SEO parity (non-negotiable for the pilot)
The current sites carry real organic search value — nothing here can regress that:
- URL structure: match existing paths where reasonable (e.g.
/venues/{slug},/events/{slug}); anywhere a path must change, a 301 redirect map ships in the same release, not after- Note in HeyAustin/CrestedButte/LakeTravis: venues currently live at
/{region-slug}/{venue-slug}/(e.g./lake-travis/bella-loma-.../) — the region-in-path pattern is worth preserving since it's already indexed
- Note in HeyAustin/CrestedButte/LakeTravis: venues currently live at
- Structured data:
LocalBusiness+GeoCoordinatesfor venues,Event(schema.org) for events,AggregateRatingfor both — matches what's already live today, so this is parity, not a new bet - Sitemaps: generated dynamically from the DB (
/sitemap.xmlper site), not hand-maintained - Canonical tags, proper pagination (
rel=next/prevequivalents),robots.txtper site
Search & browse
- Faceted structured browsing (category, region, date range) as the primary navigation — always available, always fast, never dependent on an LLM call
- The AI search endpoint (
/v1/search) layers on top as a prominent search bar for natural-language queries — augments structured browse, doesn't replace it. Users who know what they want click filters; users who don't type a sentence.
Maps
Keep Mapbox GL (already proven on the current sites) for the interactive
map view (directory map, "near me", individual venue location pins) — no
reason to switch mapping providers, venues.lat/lng already model this.
Featured content
Homepage hero/featured rail per site driven by is_featured (curator-set in
the admin portal) — not a hardcoded homepage, so curators control what's
promoted without a code deploy.
Navigation and homepage (confirmed against the HeyAustin design mockup)
Top nav: Events, Venues, Artists, Blog, Deals, Areas (dropdown) — Artists
and Deals are content types added to the schema specifically because this
mockup surfaced them (event cards credit performers; a "Half Price
Cocktails, 4-6 PM" card is a recurring offer, not a one-time event). Areas
maps directly to regions.
Homepage sections, top to bottom:
- Hero with two CTAs (Browse Events / Explore Areas) — no functional design need here beyond routing to the existing browse pages
- Quick-filter category chips (Live Music, Happy Hour, Food & Drink,
Festivals, Comedy, Family, Outdoors) — this is the clean, curated event
category set the data audit recommended in place of the legacy
event_typefolksonomy; the mockup effectively confirms the exact list - Trending Events rail —
is_featuredevents, each with a favorite (heart) toggle hitting/v1/me/favorites - Explore Areas rail —
regions, image + name cards - Newsletter capture ("Never Miss Out") — writes to
newsletter_subscribers; actual sending is a third-party service (Resend/Mailchimp), not built here - App download promotion (App Store/Google Play badges)
Footer includes "Submit an Event" — a public form creating an events row
with source = 'user_submitted', landing in the same review queue as
scraped/AI-ingested events (see ai-ingestion-pipeline.md) rather than
publishing directly.
Handoff to mobile apps
The two mobile apps are out of scope for this Next.js work — they're
separate native/Flutter clients — but they consume the same /v1 API this
frontend uses. Whenever the apps get rebuilt against the new backend
(tracked separately, likely late in the migration), they're pointed at the
same single API, not a frontend-specific one.