Skip to content

Commit 2a319b1

Browse files
committed
feat: add mdx endpoint
1 parent 6aab7ee commit 2a319b1

File tree

2 files changed

+144
-1
lines changed

2 files changed

+144
-1
lines changed

app/api/mdx/route.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { PATTERNS_MAP } from "@/app/_constants/patterns";
2+
import { i18n, type Locale } from "@app/_dictionaries/i18n-config";
3+
import * as Sentry from "@sentry/nextjs";
4+
import { existsSync, readFileSync } from 'fs';
5+
import { NextRequest, NextResponse } from "next/server";
6+
import type { MdxFile } from 'nextra';
7+
import { getPageMap } from 'nextra/page-map';
8+
import { importPage } from 'nextra/pages';
9+
import { join } from 'path';
10+
11+
type MdxContent = {
12+
route: string
13+
name: string
14+
frontMatter: Record<string, unknown> | null
15+
metadata: {
16+
title?: string
17+
description?: string
18+
[key: string]: unknown
19+
}
20+
toc: {
21+
value: string
22+
url: string
23+
depth: number
24+
}[]
25+
content: string
26+
}
27+
28+
async function getAllMdxContent(locale: string) {
29+
try {
30+
const blogPages = ((await getPageMap(`/${locale}/blog`)) || []).filter(page => 'name' in page) as MdxFile[];
31+
32+
// Get all pattern categories and check if they exist first
33+
const patternCategories = Object.values(PATTERNS_MAP)
34+
.map(p => p.path)
35+
.filter(category => {
36+
const categoryPath = join(process.cwd(), 'content', locale, 'patterns', category);
37+
return existsSync(categoryPath);
38+
});
39+
40+
const patternPages = (await Promise.all(
41+
patternCategories.map(async category => {
42+
try {
43+
const pageMap = await getPageMap(`/${locale}/patterns/${category}`);
44+
return (pageMap || []).filter(page => 'name' in page);
45+
} catch (error) {
46+
console.log(`No content found for category: ${category}`);
47+
return [];
48+
}
49+
})
50+
)).flat() as MdxFile[];
51+
52+
const allContent: MdxContent[] = [];
53+
54+
const processPages = async (pages: MdxFile[]) => {
55+
for (const page of pages) {
56+
if (page.name === 'index') continue;
57+
58+
try {
59+
const path = page.route.split('/').filter(Boolean);
60+
const result = await importPage(path, locale);
61+
62+
const filePath = join(process.cwd(), 'content', locale, ...path) + '.mdx';
63+
const rawContent = readFileSync(filePath, 'utf-8');
64+
65+
// Clean the content but preserve markdown
66+
const cleanContent = rawContent
67+
.replace(/import\s+.*?;/g, '') // Remove import statements
68+
.replace(/<[^>]*>/g, '') // Remove JSX/HTML tags
69+
.replace(/\n{3,}/g, '\n\n') // Replace multiple newlines with double newlines
70+
.replace(/\s+/g, ' ') // Replace multiple spaces with single space
71+
.replace(/\n +/g, '\n') // Remove spaces after newlines
72+
.replace(/```[\s\S]*?```/g, '') // Remove code blocks
73+
.trim();
74+
75+
if (result) {
76+
allContent.push({
77+
route: page.route,
78+
name: page.name,
79+
frontMatter: 'frontMatter' in page && page.frontMatter ? page.frontMatter : null,
80+
metadata: result.metadata,
81+
toc: result.toc,
82+
content: cleanContent
83+
});
84+
}
85+
} catch (error) {
86+
console.error(`Error importing page ${page.route}:`, error);
87+
}
88+
}
89+
};
90+
91+
await Promise.all([
92+
processPages(blogPages),
93+
processPages(patternPages)
94+
]);
95+
96+
return allContent;
97+
} catch (error) {
98+
console.error('Error in getAllMdxContent:', error);
99+
throw new Error('Failed to get MDX content');
100+
}
101+
}
102+
103+
export async function GET(req: NextRequest) {
104+
try {
105+
// Check API key
106+
const authHeader = req.headers.get('authorization');
107+
const apiKey = process.env.MDX_API_KEY;
108+
109+
if (!authHeader || authHeader !== `Bearer ${apiKey}`) {
110+
return NextResponse.json(
111+
{ error: 'Unauthorized' },
112+
{ status: 401 }
113+
);
114+
}
115+
116+
// Get locale from query params or default to 'en'
117+
const searchParams = req.nextUrl.searchParams;
118+
const locale = (searchParams.get('locale') || i18n.defaultLocale) as Locale;
119+
120+
if (!i18n.locales.includes(locale)) {
121+
return NextResponse.json(
122+
{ error: 'Invalid locale' },
123+
{ status: 400 }
124+
);
125+
}
126+
127+
const content = await getAllMdxContent(locale);
128+
129+
return NextResponse.json({
130+
content,
131+
success: true
132+
});
133+
134+
} catch (error) {
135+
Sentry.captureException(error);
136+
console.error(error);
137+
138+
return NextResponse.json(
139+
{ error: 'Internal server error' },
140+
{ status: 500 }
141+
);
142+
}
143+
}

middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ export { middleware } from 'nextra/locales'
33
export const config = {
44
// Matcher ignoring `/_next/` and `/api/`
55
matcher: [
6-
'/((?!api|_next/static|_next/image|favicon.ico|opengraph-image.png|twitter-image.png|sitemap.xml|apple-icon.png|manifest|_pagefind).*)'
6+
'/((?!api/mdx|_next/static|_next/image|favicon.ico|opengraph-image.png|twitter-image.png|sitemap.xml|apple-icon.png|manifest|_pagefind).*)'
77
]
88
}

0 commit comments

Comments
 (0)