Skip to content

Commit f03ccef

Browse files
ericyangpanclaude
andcommitted
refactor: migrate MDX components to dynamic imports
Convert MDX component loading from synchronous require() calls to async dynamic imports for improved code splitting and performance. Changes: - Update generate-metadata.mjs to generate dynamic import syntax - Convert getArticleComponents/getDocComponents to getArticleComponent/getDocComponent - Change component retrieval functions to async - Update all page components to await component loading - Remove @typescript-eslint/no-require-imports rule from ESLint config This migration enables better chunk optimization and reduces initial bundle size by loading MDX content on demand. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 539f2f9 commit f03ccef

File tree

9 files changed

+71
-60
lines changed

9 files changed

+71
-60
lines changed

eslint.config.mjs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@ const compat = new FlatCompat({
1111

1212
const eslintConfig = [
1313
...compat.extends("next/core-web-vitals", "next/typescript"),
14-
{
15-
rules: {
16-
// Allow require() for MDX component imports (webpack handles these at build time)
17-
"@typescript-eslint/no-require-imports": "off",
18-
},
19-
},
2014
];
2115

2216
export default eslintConfig;

scripts/generate-metadata.mjs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -246,15 +246,15 @@ function generateArticleComponentsCode(articles) {
246246
for (const locale of locales) {
247247
const localeArticles = articles[locale];
248248
const componentEntries = localeArticles.map(article =>
249-
` '${article.slug}': require('@content/articles/${locale}/${article.slug}.mdx').default,`
249+
` '${article.slug}': () => import('@content/articles/${locale}/${article.slug}.mdx'),`
250250
).join('\n');
251251

252252
if (componentEntries) {
253253
componentLines.push(` '${locale}': {\n${componentEntries}\n },`);
254254
}
255255
}
256256

257-
return `const articleComponents: Record<string, Record<string, React.ComponentType>> = {\n${componentLines.join('\n')}\n};`;
257+
return `const articleComponents: Record<string, Record<string, () => Promise<{ default: React.ComponentType }>>> = {\n${componentLines.join('\n')}\n};`;
258258
}
259259

260260
// Generate component imports for docs
@@ -265,15 +265,15 @@ function generateDocComponentsCode(docs) {
265265
for (const locale of locales) {
266266
const localeDocs = docs[locale];
267267
const componentEntries = localeDocs.map(doc =>
268-
` '${doc.slug}': require('@content/docs/${locale}/${doc.slug}.mdx').default,`
268+
` '${doc.slug}': () => import('@content/docs/${locale}/${doc.slug}.mdx'),`
269269
).join('\n');
270270

271271
if (componentEntries) {
272272
componentLines.push(` '${locale}': {\n${componentEntries}\n },`);
273273
}
274274
}
275275

276-
return `const docComponents: Record<string, Record<string, React.ComponentType>> = {\n${componentLines.join('\n')}\n};`;
276+
return `const docComponents: Record<string, Record<string, () => Promise<{ default: React.ComponentType }>>> = {\n${componentLines.join('\n')}\n};`;
277277
}
278278

279279
// Generate component import for manifesto (single index.mdx per locale)
@@ -283,11 +283,11 @@ function generateManifestoComponentsCode() {
283283
for (const locale of SUPPORTED_LOCALES) {
284284
const manifestoIndex = path.join(rootDir, `content/manifesto/${locale}/index.mdx`);
285285
if (fs.existsSync(manifestoIndex)) {
286-
componentLines.push(` '${locale}': require('@content/manifesto/${locale}/index.mdx').default,`);
286+
componentLines.push(` '${locale}': () => import('@content/manifesto/${locale}/index.mdx'),`);
287287
}
288288
}
289289

290-
return ` const components: Record<string, React.ComponentType> = {\n${componentLines.join('\n')}\n };`;
290+
return ` const components: Record<string, () => Promise<{ default: React.ComponentType }>> = {\n${componentLines.join('\n')}\n };`;
291291
}
292292

293293
// Main execution
@@ -359,12 +359,16 @@ export function getArticleBySlug(slug: string, locale: string = 'en'): ArticleMe
359359
return localeArticles.find((article) => article.slug === slug);
360360
}
361361
362-
// MDX components mapping for all locales (webpack will handle this at build time)
362+
// MDX components mapping for all locales (dynamic imports)
363363
${articlesComponentsCode}
364364
365-
// Get components for a specific locale with fallback to English
366-
export function getArticleComponents(locale: string = 'en'): Record<string, React.ComponentType> {
367-
return articleComponents[locale] || articleComponents['en'] || {};
365+
// Get a specific article component for a given locale and slug
366+
export async function getArticleComponent(locale: string = 'en', slug: string): Promise<React.ComponentType | null> {
367+
const loaders = articleComponents[locale] || articleComponents['en'];
368+
const loader = loaders?.[slug];
369+
if (!loader) return null;
370+
const mdxModule = await loader();
371+
return mdxModule.default;
368372
}
369373
`;
370374

@@ -397,12 +401,16 @@ export function getDocBySlug(slug: string, locale: string = 'en'): DocSection |
397401
return sections.find((doc) => doc.slug === slug);
398402
}
399403
400-
// MDX components mapping for all locales (webpack will handle this at build time)
404+
// MDX components mapping for all locales (dynamic imports)
401405
${docsComponentsCode}
402406
403-
// Get components for a specific locale with fallback to English
404-
export function getDocComponents(locale: string = 'en'): Record<string, React.ComponentType> {
405-
return docComponents[locale] || docComponents['en'] || {};
407+
// Get a specific doc component for a given locale and slug
408+
export async function getDocComponent(locale: string = 'en', slug: string): Promise<React.ComponentType | null> {
409+
const loaders = docComponents[locale] || docComponents['en'];
410+
const loader = loaders?.[slug];
411+
if (!loader) return null;
412+
const mdxModule = await loader();
413+
return mdxModule.default;
406414
}
407415
`;
408416

@@ -417,10 +425,12 @@ export function getDocComponents(locale: string = 'en'): Record<string, React.Co
417425
* Return the Manifesto MDX React component for a given locale.
418426
* Falls back to the default locale ('en') when the requested locale is missing.
419427
*/
420-
export function getManifestoComponent(locale: string = 'en'): React.ComponentType {
428+
export async function getManifestoComponent(locale: string = 'en'): Promise<React.ComponentType> {
421429
${manifestoComponentsCode}
422430
423-
return components[locale] || components['en'];
431+
const loader = components[locale] || components['en'];
432+
const mdxModule = await loader();
433+
return mdxModule.default;
424434
}
425435
`;
426436

src/app/[locale]/ai-coding-stack/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type Props = {
4646
export default async function AICodingStackPage({ params }: Props) {
4747
const { locale } = await params;
4848
const t = await getTranslations({ locale, namespace: 'stacksPages.overview' });
49-
const ManifestoContent = getManifestoComponent(locale);
49+
const ManifestoContent = await getManifestoComponent(locale);
5050

5151
return (
5252
<>

src/app/[locale]/articles/[slug]/page.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Link } from '@/i18n/navigation';
22
import { notFound } from 'next/navigation';
33
import Header from '@/components/Header';
44
import Footer from '@/components/Footer';
5-
import { getArticles, getArticleBySlug, getArticleComponents } from '@/lib/generated/articles';
5+
import { getArticles, getArticleBySlug, getArticleComponent } from '@/lib/generated/articles';
66
import { generateArticleMetadata } from '@/lib/metadata';
77

88
type Props = {
@@ -53,8 +53,7 @@ export default async function ArticlePage({ params }: Props) {
5353
notFound();
5454
}
5555

56-
const articleComponents = getArticleComponents(locale);
57-
const ArticleContent = articleComponents[slug];
56+
const ArticleContent = await getArticleComponent(locale, slug);
5857

5958
if (!ArticleContent) {
6059
return (

src/app/[locale]/docs/[slug]/page.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { notFound } from 'next/navigation';
33
import Header from '@/components/Header';
44
import Footer from '@/components/Footer';
55
import DocsSidebar from '@/components/sidebar/DocsSidebar';
6-
import { getDocSections, getDocBySlug, getDocComponents } from '@/lib/generated/docs';
6+
import { getDocSections, getDocBySlug, getDocComponent } from '@/lib/generated/docs';
77
import { generateDocsMetadata } from '@/lib/metadata';
88

99
type Props = {
@@ -53,8 +53,7 @@ export default async function DocPage({ params }: Props) {
5353
}
5454

5555
const docSections = getDocSections(locale);
56-
const docComponents = getDocComponents(locale);
57-
const DocContent = docComponents[slug];
56+
const DocContent = await getDocComponent(locale, slug);
5857

5958
if (!DocContent) {
6059
return (

src/app/[locale]/docs/page.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Header from '@/components/Header';
22
import Footer from '@/components/Footer';
33
import DocsSidebar from '@/components/sidebar/DocsSidebar';
4-
import { getDocSections, getDocComponents } from '@/lib/generated/docs';
4+
import { getDocSections, getDocComponent } from '@/lib/generated/docs';
55
import { getTranslations } from 'next-intl/server';
66
import { buildCanonicalUrl, buildOpenGraph, buildTwitterCard, buildTitle } from '@/lib/metadata';
77

@@ -45,8 +45,7 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
4545
export default async function DocsPage({ params }: Props) {
4646
const { locale } = await params;
4747
const docSections = getDocSections(locale);
48-
const docComponents = getDocComponents(locale);
49-
const WelcomeDoc = docComponents['welcome'];
48+
const WelcomeDoc = await getDocComponent(locale, 'welcome');
5049
return (
5150
<>
5251
<Header />

src/lib/generated/articles.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,27 @@ export function getArticleBySlug(slug: string, locale: string = 'en'): ArticleMe
2424
return localeArticles.find((article) => article.slug === slug);
2525
}
2626

27-
// MDX components mapping for all locales (webpack will handle this at build time)
28-
const articleComponents: Record<string, Record<string, React.ComponentType>> = {
27+
// MDX components mapping for all locales (dynamic imports)
28+
const articleComponents: Record<string, Record<string, () => Promise<{ default: React.ComponentType }>>> = {
2929
'en': {
30-
'getting-started-with-ai-coding': require('@content/articles/en/getting-started-with-ai-coding.mdx').default,
31-
'mcp-servers-explained': require('@content/articles/en/mcp-servers-explained.mdx').default,
30+
'getting-started-with-ai-coding': () => import('@content/articles/en/getting-started-with-ai-coding.mdx'),
31+
'mcp-servers-explained': () => import('@content/articles/en/mcp-servers-explained.mdx'),
3232
},
3333
'zh-Hans': {
34-
'getting-started-with-ai-coding': require('@content/articles/zh-Hans/getting-started-with-ai-coding.mdx').default,
35-
'mcp-servers-explained': require('@content/articles/zh-Hans/mcp-servers-explained.mdx').default,
34+
'getting-started-with-ai-coding': () => import('@content/articles/zh-Hans/getting-started-with-ai-coding.mdx'),
35+
'mcp-servers-explained': () => import('@content/articles/zh-Hans/mcp-servers-explained.mdx'),
3636
},
3737
'de': {
38-
'getting-started-with-ai-coding': require('@content/articles/de/getting-started-with-ai-coding.mdx').default,
39-
'mcp-servers-explained': require('@content/articles/de/mcp-servers-explained.mdx').default,
38+
'getting-started-with-ai-coding': () => import('@content/articles/de/getting-started-with-ai-coding.mdx'),
39+
'mcp-servers-explained': () => import('@content/articles/de/mcp-servers-explained.mdx'),
4040
},
4141
};
4242

43-
// Get components for a specific locale with fallback to English
44-
export function getArticleComponents(locale: string = 'en'): Record<string, React.ComponentType> {
45-
return articleComponents[locale] || articleComponents['en'] || {};
43+
// Get a specific article component for a given locale and slug
44+
export async function getArticleComponent(locale: string = 'en', slug: string): Promise<React.ComponentType | null> {
45+
const loaders = articleComponents[locale] || articleComponents['en'];
46+
const loader = loaders?.[slug];
47+
if (!loader) return null;
48+
const mdxModule = await loader();
49+
return mdxModule.default;
4650
}

src/lib/generated/docs.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,27 @@ export function getDocBySlug(slug: string, locale: string = 'en'): DocSection |
2323
return sections.find((doc) => doc.slug === slug);
2424
}
2525

26-
// MDX components mapping for all locales (webpack will handle this at build time)
27-
const docComponents: Record<string, Record<string, React.ComponentType>> = {
26+
// MDX components mapping for all locales (dynamic imports)
27+
const docComponents: Record<string, Record<string, () => Promise<{ default: React.ComponentType }>>> = {
2828
'en': {
29-
'getting-started': require('@content/docs/en/getting-started.mdx').default,
30-
'welcome': require('@content/docs/en/welcome.mdx').default,
29+
'getting-started': () => import('@content/docs/en/getting-started.mdx'),
30+
'welcome': () => import('@content/docs/en/welcome.mdx'),
3131
},
3232
'zh-Hans': {
33-
'getting-started': require('@content/docs/zh-Hans/getting-started.mdx').default,
34-
'welcome': require('@content/docs/zh-Hans/welcome.mdx').default,
33+
'getting-started': () => import('@content/docs/zh-Hans/getting-started.mdx'),
34+
'welcome': () => import('@content/docs/zh-Hans/welcome.mdx'),
3535
},
3636
'de': {
37-
'getting-started': require('@content/docs/de/getting-started.mdx').default,
38-
'welcome': require('@content/docs/de/welcome.mdx').default,
37+
'getting-started': () => import('@content/docs/de/getting-started.mdx'),
38+
'welcome': () => import('@content/docs/de/welcome.mdx'),
3939
},
4040
};
4141

42-
// Get components for a specific locale with fallback to English
43-
export function getDocComponents(locale: string = 'en'): Record<string, React.ComponentType> {
44-
return docComponents[locale] || docComponents['en'] || {};
42+
// Get a specific doc component for a given locale and slug
43+
export async function getDocComponent(locale: string = 'en', slug: string): Promise<React.ComponentType | null> {
44+
const loaders = docComponents[locale] || docComponents['en'];
45+
const loader = loaders?.[slug];
46+
if (!loader) return null;
47+
const mdxModule = await loader();
48+
return mdxModule.default;
4549
}

src/lib/generated/manifesto.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
* Return the Manifesto MDX React component for a given locale.
66
* Falls back to the default locale ('en') when the requested locale is missing.
77
*/
8-
export function getManifestoComponent(locale: string = 'en'): React.ComponentType {
9-
const components: Record<string, React.ComponentType> = {
10-
'en': require('@content/manifesto/en/index.mdx').default,
11-
'zh-Hans': require('@content/manifesto/zh-Hans/index.mdx').default,
12-
'de': require('@content/manifesto/de/index.mdx').default,
8+
export async function getManifestoComponent(locale: string = 'en'): Promise<React.ComponentType> {
9+
const components: Record<string, () => Promise<{ default: React.ComponentType }>> = {
10+
'en': () => import('@content/manifesto/en/index.mdx'),
11+
'zh-Hans': () => import('@content/manifesto/zh-Hans/index.mdx'),
12+
'de': () => import('@content/manifesto/de/index.mdx'),
1313
};
1414

15-
return components[locale] || components['en'];
15+
const loader = components[locale] || components['en'];
16+
const mdxModule = await loader();
17+
return mdxModule.default;
1618
}

0 commit comments

Comments
 (0)