Landing Page Cleanup — Extract Inline Sections to Components
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Refactor 7 page.tsx files in apps/frontend/app/[locale]/(landing)/ so each page only composes extracted section components — no inline data arrays, helper functions, or JSX section blocks.
Architecture: Each page's inline sections become a new SectionName component in components/[domain]/. Page.tsx becomes a thin orchestrator. Shared data arrays go to lib/data/. Helper functions with business logic go to lib/utils/.
Tech Stack: Next.js 16 (App Router), Tailwind CSS v4, paraglide-js i18n
Scope: Pages That Need Refactoring
| Page | Lines | Issues |
|---|---|---|
about/page.tsx | 164 | Inline content blocks (storyContent, cultureContent, etc.) |
venue-rental/page.tsx | 381 | Inline data arrays + inline JSX sections |
private-events/page.tsx | 294 | Inline data arrays + helper functions + inline JSX sections |
workshops/page.tsx | 294 | Inline data arrays + idealForTitle() + inline JSX sections |
artist-proposal/page.tsx | 319 | Inline data arrays + performanceTypeTitle() + inline JSX sections |
artists/page.tsx | 127 | Inline performance-type cards + inline venue feature cards |
inquiry/contact/page.tsx | 114 | Inline contactItems array |
Task 1: Extract about/page.tsx
Target: components/marketing/about/ directory
Files:
- Create:
components/marketing/about/story-section.tsx - Create:
components/marketing/about/culture-section.tsx - Create:
components/marketing/about/community-section.tsx - Create:
components/marketing/about/vision-section.tsx - Modify:
app/[locale]/(landing)/about/page.tsx
Pattern to extract:
-
storyContentJSX block →StorySection.tsx -
cultureContentJSX block →CultureSection.tsx -
communityContentJSX block →CommunitySection.tsx -
visionContentJSX block →VisionSection.tsx -
Step 1: Create
components/marketing/about/story-section.tsx
Extract storyContent JSX and the surrounding ContentSection into StorySection. Keep the same props: id, title, content, imageSrc, imageAlt, contentClassName, titleAlign, spacing.
// components/marketing/about/story-section.tsx
import { ContentSection } from "~/components/ui/content-section";
import { m } from "~/src/paraglide/messages";
const storyContent = (
<>
<p>House of Legends was created by a couple...</p>
{/* rest of storyContent JSX */}
</>
);
export function StorySection() {
return (
<ContentSection
id="our-story"
title={m.about_ourStory()}
content={storyContent}
imageSrc="/images/about-hol-1.jpg"
imageAlt="House of Legends interior"
contentClassName="order-1"
titleAlign="left"
spacing="lg"
/>
);
}- Step 2: Create remaining section components
Repeat pattern for culture-section.tsx, community-section.tsx, vision-section.tsx.
- Step 3: Rewrite
about/page.tsx
"use client";
import { StorySection } from "~/components/marketing/about/story-section";
import { CultureSection } from "~/components/marketing/about/culture-section";
import { CommunitySection } from "~/components/marketing/about/community-section";
import { VisionSection } from "~/components/marketing/about/vision-section";
import { SectionDivider } from "~/components/ui/section-divider";
import { VisitCTASection } from "~/components/marketing/visit-cta-section";
export default function AboutPage() {
return (
<main className="min-h-screen bg-background text-foreground pt-[var(--header-height)]">
<StorySection />
<SectionDivider />
<CultureSection />
<SectionDivider />
<CommunitySection />
<SectionDivider />
<VisionSection />
<SectionDivider />
<VisitCTASection />
</main>
);
}Task 2: Extract venue-rental/page.tsx
Target: components/marketing/venue-rental/ directory
Files:
- Create:
components/marketing/venue-rental/venue-intro-section.tsx(hero + first content section) - Create:
components/marketing/venue-rental/venue-form-section.tsx(inquiry form section) - Create:
components/marketing/venue-rental/venue-showcase-section.tsx(numbered cards + show formats) - Modify:
app/[locale]/(landing)/inquiry/venue-rental/page.tsx
Inline data: Keep arrays inline in their section components — no lib/data/ files. Only create lib/data/ for truly shared data across multiple pages.
- Step 1: Extract section components
Create VenueIntroSection, VenueFormSection, VenueShowcaseSection by extracting the relevant inline JSX blocks from page.tsx. Each section should import from lib/data/ directly.
- Step 4: Rewrite
venue-rental/page.tsx
Remove all inline data arrays and inline JSX sections. Import extracted section components. Page.tsx should be under 40 lines.
Task 3: Extract private-events/page.tsx
Target: components/marketing/private-events/ directory
Files:
- Create:
components/marketing/private-events/private-events-experience-section.tsx - Create:
components/marketing/private-events/private-events-venue-section.tsx - Create:
components/marketing/private-events/private-events-packages-section.tsx - Create:
components/marketing/private-events/private-events-form-section.tsx - Modify:
app/[locale]/(landing)/inquiry/private-events/page.tsx
Inline helpers to remove:
-
packageTitle(),packageFeature(),amenityLabel(),experienceTitle(),experienceDescription()— these map i18n keys. Move the logic into each section component using paraglidem.*calls directly. -
Step 1: Extract section components
Each section becomes its own component. Pass i18n keys directly as props or use m.* calls inside the section.
- Step 2: Rewrite
private-events/page.tsx
Remove all helper functions and inline data. Page should compose extracted sections.
Task 4: Extract workshops/page.tsx
Target: components/marketing/workshops/ directory
Files:
- Create:
components/marketing/workshops/workshop-organizers-section.tsx - Create:
components/marketing/workshops/workshop-ideal-for-section.tsx(IconCardsGrid withidealIcons) - Create:
components/marketing/workshops/workshop-why-host-section.tsx(3 Why Host cards) - Create:
components/marketing/workshops/workshop-formats-section.tsx - Create:
components/marketing/workshops/workshop-form-section.tsx - Modify:
app/[locale]/(landing)/inquiry/workshops/page.tsx
Inline data: Keep arrays inline in section components. Only use lib/data/ for data shared across multiple pages.
Helper to remove:
-
idealForTitle(key: number)→ replace with directm.*calls insideWorkshopIdealForSection -
Step 1: Create section components following the same extraction pattern.
Task 5: Extract artist-proposal/page.tsx
Target: components/marketing/artist-proposal/ directory
Files:
- Create:
components/marketing/artist-proposal/artist-types-section.tsx - Create:
components/marketing/artist-proposal/artist-venue-section.tsx - Create:
components/marketing/artist-proposal/artist-show-formats-section.tsx - Create:
components/marketing/artist-proposal/artist-highlights-section.tsx - Create:
components/marketing/artist-proposal/artist-gallery-section.tsx - Create:
components/marketing/artist-proposal/artist-form-section.tsx - Modify:
app/[locale]/(landing)/inquiry/artist-proposal/page.tsx
Inline data: Keep arrays inline in section components.
Helper to remove:
performanceTypeTitle(key)→ replace with directm.*calls
Task 6: Extract artists/page.tsx
Target: components/marketing/artists/ directory
Files:
- Create:
components/marketing/artists/artists-performance-types-section.tsx - Create:
components/marketing/artists/artists-venue-section.tsx - Create:
components/marketing/artists/artists-cta-section.tsx - Modify:
app/[locale]/(landing)/artists/page.tsx
Inline JSX to extract:
- Performance types grid (6 cards with emoji) →
PerformanceTypesSection - Venue features grid (3 cards) →
VenueSection
Note: Performance type cards use emoji icons (🎵, 🎭, etc.) — these should be replaced with IconSymbol per the no-emoji rule. Use IconSymbol with appropriate SF Symbol names (music.note, theater.fill, etc.) instead.
- Step 1: Replace emoji in performance type cards with IconSymbol
In artists-performance-types-section.tsx, replace:
// BAD — emoji
<div className="text-3xl mb-2">🎵</div>
// GOOD — IconSymbol
<IconSymbol name="music.note" size={24} className="mb-2" />-
Step 2: Extract venue features into
VenueSectioncomponent -
Step 3: Rewrite
artists/page.tsx
Task 7: Extract inquiry/contact/page.tsx
Target: components/marketing/contact/ directory
Files:
-
Create:
components/marketing/contact/contact-info-section.tsx -
Create:
components/marketing/contact/contact-map-section.tsx -
Modify:
app/[locale]/(landing)/inquiry/contact/page.tsx -
Step 1: Extract sections
-
Step 2: Extract sections and rewrite
contact/page.tsx
Implementation Notes
Page-level state (what stays in page.tsx)
useStatefor modal control (e.g.,french-mentalist)nuqsquery params (e.g.,wall/page.tsx)CalendarModalProvidercontext wrapping
What goes into section components
- All inline JSX section blocks
- All inline data arrays (keep inline in the section — no
lib/data/unless shared across pages) - All helper functions that are purely i18n mappers (inline directly, use
m.*calls)
Naming convention for extracted sections
components/[domain]/[section-name]-section.tsx- Export:
export function [SectionName]Section() { ... } - Examples:
StorySection,CultureSection,VenueIntroSection,WorkshopIdealForSection
Order of extraction (follow this order)
- Section components (data arrays stay inline)
- Rewrite
page.tsx(import sections, remove inline code)
Rollback plan
If a page breaks during refactoring, restore from git:
git checkout -- apps/frontend/app/[locale]/(landing)/[page]/page.tsxSections in components/ can be left in place — they're additive improvements.