plans
2026-05-05
2026 05 05 Mdx Content Migration

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 PageMDX wrapper
  • 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.mts already supports pageExtensions: ["ts", "tsx"] — verify .mdx is 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 grids
  • reviews — Carousel with dynamic data, external Google link
  • about-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.tsx

Extract 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.tsx

Extract 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.mts includes "mdx" in pageExtensions
  • mdx-components.tsx exports custom FaqAccordion component
  • PageMDX component created with PageHero + 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

RequirementTask
MDX config verified/updatedTask 0
PageMDX wrapper componentTask 1
FaqAccordion custom MDX componentTask 2
FAQ page to MDXTask 3
Contact page to MDXTask 4
Host-an-Event page to MDXTask 5
Private-Events page to MDXTask 6
Our-Evening to MDXTask 7
Dinner-Theater to MDXTask 8
Creative-Workshop to MDXTask 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