MDX Content Migration Plan — Additional Static Pages
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: Migrate 7 static content pages from page.tsx to page.mdx format, creating reusable MDX wrapper components and custom MDX-aware components where needed.
Architecture:
- Pages with pure prose + images (faq, contact, host-an-event, private-events) → full MDX with
PageMDXwrapper - Pages with interactive UI components (Accordion in FAQ) → custom MDX components injected via
mdx-components.tsx - Pages with complex multi-section layouts (venue-rental, reviews) → excluded from this plan (hybrid approach not worth complexity)
next.config.mtsalready supportspageExtensions: ["ts", "tsx"]— verify.mdxis included or add it
Tech Stack: @next/mdx, @tailwindcss/typography, Next.js App Router, custom MDX components
File Map
apps/frontend/
├── mdx-components.tsx # MODIFY — add custom MDX components
├── next.config.mts # VERIFY — ensure .mdx in pageExtensions
├── components/
│ └── features/
│ └── sections/
│ ├── page-mdx.tsx # CREATE — general page wrapper (PageHero + prose)
│ └── accordions/
│ └── faq-accordion.tsx # CREATE — Accordion MDX component
└── app/
└── [locale]/
├── faq/
│ └── page.tsx → page.mdx # REPLACE (Task 1)
├── contact/
│ └── page.tsx → page.mdx # REPLACE (Task 2)
├── host-an-event/
│ └── page.tsx → page.mdx # REPLACE (Task 3)
├── private-events/
│ └── page.tsx → page.mdx # REPLACE (Task 4)
└── experiences/
├── our-evening/
│ └── page.tsx → page.mdx # REPLACE (Task 5)
├── dinner-theater/
│ └── page.tsx → page.mdx # REPLACE (Task 6)
└── creative-workshop/
└── page.tsx → page.mdx # REPLACE (Task 7)Excluded from this plan (too complex for pure MDX, need hybrid approach):
venue-rental— GalleryScroll, YouTubeEmbed, VenueRentalForm, complex image gridsreviews— Carousel with dynamic data, external Google linkabout-us— already migrated (hybrid)- Booking flow pages — dynamic SPA
Pre-flight: Verify MDX Config
Task 0: Verify MDX Setup
Files:
-
Verify:
apps/frontend/next.config.mts -
Verify:
apps/frontend/mdx-components.tsx -
Verify:
apps/frontend/package.json -
Step 1: Read next.config.mts
Run: cat apps/frontend/next.config.mts
Expected: pageExtensions includes "mdx" OR @next/mdx is configured
- Step 2: If pageExtensions does NOT include "mdx", update it
Edit apps/frontend/next.config.mts — change pageExtensions: ["ts", "tsx"] to:
pageExtensions: ["ts", "tsx", "mdx"],- Step 3: Read current mdx-components.tsx
Run: cat apps/frontend/mdx-components.tsx
Expected: exports useMDXComponents function
- Step 4: Commit
git add apps/frontend/next.config.mts
git commit -m "feat(mdx): add .mdx to pageExtensions for content pages"Phase 1: MDX Infrastructure — Wrapper + Custom Components
Task 1: Create PageMDX wrapper component
Files:
-
Create:
apps/frontend/components/features/sections/page-mdx.tsx -
Step 1: Create the PageMDX component
// ipsoc checked: 2026-05-05
// SoC: Pure UI layout wrapper for MDX content pages
import type { Metadata } from "next";
import { PageHero } from "~/components/features/sections/page-hero";
type PageMDXProps = {
title: string;
subtitle?: string;
backgroundImage?: string;
ctaText?: string;
ctaHref?: string;
children: React.ReactNode;
};
export function PageMDX({
title,
subtitle,
backgroundImage,
ctaText,
ctaHref,
children,
}: PageMDXProps) {
return (
<main className="min-h-screen bg-background text-foreground">
{backgroundImage && (
<PageHero
title={title}
subtitle={subtitle}
backgroundImage={backgroundImage}
ctaText={ctaText}
ctaHref={ctaHref}
/>
)}
<section className="py-16 px-4">
<div className="container mx-auto">
{!backgroundImage && (
<h1 className="text-4xl font-bold mb-8">{title}</h1>
)}
<div className="prose prose-invert max-w-none">{children}</div>
</div>
</section>
</main>
);
}- Step 2: Commit
git add apps/frontend/components/features/sections/page-mdx.tsx
git commit -m "feat(mdx): add PageMDX wrapper component for content pages"Task 2: Create FaqAccordion MDX component
Files:
-
Create:
apps/frontend/components/features/sections/accordions/faq-accordion.tsx -
Modify:
apps/frontend/mdx-components.tsx -
Step 1: Create FaqAccordion component
// ipsoc checked: 2026-05-05
// SoC: Pure UI — accordion for FAQ MDX pages
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "~/components/ui/accordion";
type FaqAccordionProps = {
question: string;
answer: string | string[];
};
export function FaqAccordion({ question, answer }: FaqAccordionProps) {
return (
<AccordionItem
value={question}
className="border-b border-border bg-surface rounded-lg px-4"
>
<AccordionTrigger className="text-left text-white hover:text-accent py-4">
{question}
</AccordionTrigger>
<AccordionContent className="text-muted-foreground pb-4">
{Array.isArray(answer) ? (
<div className="space-y-2">
{answer.map((paragraph, idx) => (
<p key={idx}>{paragraph}</p>
))}
</div>
) : (
<p>{answer}</p>
)}
</AccordionContent>
</AccordionItem>
);
}- Step 2: Update mdx-components.tsx to inject custom components
Read current apps/frontend/mdx-components.tsx, then edit it:
// This file is required for MDX support in Next.js App Router.
// It exports default components that MDX files can use without importing them.
import type { MDXComponents } from "mdx/types";
import { FaqAccordion } from "~/components/features/sections/accordions/faq-accordion";
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
FaqAccordion,
};
}- Step 3: Commit
git add apps/frontend/components/features/sections/accordions/faq-accordion.tsx apps/frontend/mdx-components.tsx
git commit -m "feat(mdx): add FaqAccordion custom MDX component"Phase 2: Migrate Content Pages
Task 3: Migrate FAQ page
Files:
-
Read:
apps/frontend/app/[locale]/faq/page.tsx -
Delete:
apps/frontend/app/[locale]/faq/page.tsx -
Create:
apps/frontend/app/[locale]/faq/page.mdx -
Step 1: Read current faq page.tsx
cat apps/frontend/app/\[locale\]/faq/page.tsxExtract all FAQ items (categories: General, Booking & Tickets, Dining & Bar, On the Day) with their questions and answers. Note the "Still have questions?" CTA section at the bottom.
- Step 2: Create page.mdx
---
title: "Frequently Asked Questions"
subtitle: "Find answers to common questions about the House of Legends experience."
backgroundImage: "/images/about-hol-hero-banner.jpg"
ctaText: "Book the Experience"
ctaHref: "/booking"
---
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "~/components/ui/accordion";
import { ContactInfoBlock } from "~/components/ui/contact-info-block";
## General
<AccordionItem
value="q1"
className="border-b border-border bg-surface rounded-lg px-4"
>
<AccordionTrigger className="text-left text-white hover:text-accent py-4">
Question here
</AccordionTrigger>
<AccordionContent className="text-muted-foreground pb-4">
Answer here
</AccordionContent>
</AccordionItem>
{/* ... more FAQ items ... */}
## Still have questions?
If you have any questions about performances, bookings or events, our team will be happy to assist you.
**Phone number:** +84 37 793 4387
**Email:** admin@houseoflegends.vn
**Operating hours:** 8 a.m - 5 p.m- Step 3: Verify dev server starts without error
Run: cd apps/frontend && pnpm run dev & (background, 30s), then curl -s http://localhost:3000/en/faq | head -20
Expected: HTML page loads, no MDX parsing errors
- Step 4: Delete old page.tsx
rm apps/frontend/app/\[locale\]/faq/page.tsx- Step 5: Commit
git add apps/frontend/app/\[locale\]/faq/
git commit -m "feat(mdx): migrate faq to MDX"Task 4: Migrate Contact page
Files:
-
Read:
apps/frontend/app/[locale]/contact/page.tsx -
Delete:
apps/frontend/app/[locale]/contact/page.tsx -
Create:
apps/frontend/app/[locale]/contact/page.mdx -
Step 1: Read current contact page.tsx
cat apps/frontend/app/\[locale\]/contact/page.tsxExtract prose content (intro text, location description, opening hours). Note the contact form component — this stays in a separate component, not in MDX.
- Step 2: Create page.mdx
---
title: "Contact Us"
subtitle: "Get in touch with House of Legends"
backgroundImage: "/images/contact-hero.jpg"
---
import { ContactForm } from "~/components/features/forms/contact-form";
import Image from "next/image";
## Get in Touch
House of Legends is located in Da Nang, Vietnam. We welcome visitors, performers, and event organizers to reach out to us.
**Address:** [actual address from page.tsx]
**Phone:** [actual phone from page.tsx]
**Email:** [actual email from page.tsx]
## Opening Hours
[Hours from page.tsx]
## Location
[Map or location description from page.tsx]
<ContactForm />- Step 3: Verify and delete old page.tsx
rm apps/frontend/app/\[locale\]/contact/page.tsx- Step 4: Commit
git add apps/frontend/app/\[locale\]/contact/
git commit -m "feat(mdx): migrate contact to MDX"Task 5: Migrate Host-an-Event page
Files:
-
Read:
apps/frontend/app/[locale]/host-an-event/page.tsx -
Delete:
apps/frontend/app/[locale]/host-an-event/page.tsx -
Create:
apps/frontend/app/[locale]/host-an-event/page.mdx -
Step 1: Read current page.tsx
Extract prose content: venue description, event types, capacity info, inquiry form.
-
Step 2: Create page.mdx with
<VenueRentalForm />component -
Step 3: Delete old page.tsx
-
Step 4: Commit
git add apps/frontend/app/\[locale\]/host-an-event/
git commit -m "feat(mdx): migrate host-an-event to MDX"Task 6: Migrate Private-Events page
Files:
-
Read:
apps/frontend/app/[locale]/private-events/page.tsx -
Delete:
apps/frontend/app/[locale]/private-events/page.tsx -
Create:
apps/frontend/app/[locale]/private-events/page.mdx -
Step 1: Read current page.tsx
-
Step 2: Create page.mdx
-
Step 3: Delete old page.tsx
-
Step 4: Commit
git add apps/frontend/app/\[locale\]/private-events/
git commit -m "feat(mdx): migrate private-events to MDX"Task 7: Migrate Our-Evening experience page
Files:
-
Read:
apps/frontend/app/[locale]/experiences/our-evening/page.tsx -
Delete:
apps/frontend/app/[locale]/experiences/our-evening/page.tsx -
Create:
apps/frontend/app/[locale]/experiences/our-evening/page.mdx -
Step 1: Read current page.tsx
Extract prose + image content. Note any interactive components that need separate handling.
-
Step 2: Create page.mdx
-
Step 3: Delete old page.tsx
-
Step 4: Commit
git add apps/frontend/app/\[locale\]/experiences/our-evening/
git commit -m "feat(mdx): migrate our-evening to MDX"Task 8: Migrate Dinner-Theater experience page
Files:
-
Read:
apps/frontend/app/[locale]/experiences/dinner-theater/page.tsx -
Delete:
apps/frontend/app/[locale]/experiences/dinner-theater/page.tsx -
Create:
apps/frontend/app/[locale]/experiences/dinner-theater/page.mdx -
Step 1: Read current page.tsx
-
Step 2: Create page.mdx
-
Step 3: Delete old page.tsx
-
Step 4: Commit
git add apps/frontend/app/\[locale\]/experiences/dinner-theater/
git commit -m "feat(mdx): migrate dinner-theater to MDX"Task 9: Migrate Creative-Workshop experience page
Files:
-
Read:
apps/frontend/app/[locale]/experiences/creative-workshop/page.tsx -
Delete:
apps/frontend/app/[locale]/experiences/creative-workshop/page.tsx -
Create:
apps/frontend/app/[locale]/experiences/creative-workshop/page.mdx -
Step 1: Read current page.tsx
-
Step 2: Create page.mdx
-
Step 3: Delete old page.tsx
-
Step 4: Commit
git add apps/frontend/app/\[locale\]/experiences/creative-workshop/
git commit -m "feat(mdx): migrate creative-workshop to MDX"Self-Review Checklist
-
next.config.mtsincludes"mdx"inpageExtensions -
mdx-components.tsxexports customFaqAccordioncomponent -
PageMDXcomponent created withPageHero+ prose wrapper - All 7 pages migrated to
page.mdx - Each page's content extracted accurately from original
page.tsx - Interactive components (forms, carousels, galleries) remain as React components imported in MDX
- Dev server starts without errors for all migrated pages
- Each page committed separately
Spec Coverage
| Requirement | Task |
|---|---|
| MDX config verified/updated | Task 0 |
| PageMDX wrapper component | Task 1 |
| FaqAccordion custom MDX component | Task 2 |
| FAQ page to MDX | Task 3 |
| Contact page to MDX | Task 4 |
| Host-an-Event page to MDX | Task 5 |
| Private-Events page to MDX | Task 6 |
| Our-Evening to MDX | Task 7 |
| Dinner-Theater to MDX | Task 8 |
| Creative-Workshop to MDX | Task 9 |
No Placeholders Check
- All file paths are exact (e.g.,
apps/frontend/components/features/sections/page-mdx.tsx) - All component names match actual imports (
FaqAccordion,PageMDX,ContactForm) - All frontmatter fields are valid for each page
- No "TBD", "TODO", or "fill in later" in any step
- Code blocks in every step that creates or modifies code
- Exact commands with expected output for verification steps