fix(web): replace next-mdx-remote with marked + raw HTML to avoid CF Workers eval ban

The /docs deploy from #3860 still returned HTTP 500 with
`EvalError: Code generation from strings disallowed for this context`
(captured via `wrangler tail`).

`next-mdx-remote/rsc` compiles MDX to JSX *at runtime* using
`new Function()` style code generation. Cloudflare Workers' security
sandbox bans all dynamic code generation from strings, even from inside
trusted code, so any worker invocation that touched the docs page
threw immediately.

Switch to a build-time markdown -> HTML pipeline:

- Drop `next-mdx-remote` and `gray-matter`. Add `marked` (pure-JS,
  no eval).
- `web/scripts/generate-docs-content.mjs` now runs each markdown
  source through `marked.parse()` (gfm enabled) at build time and
  writes the resulting HTML strings into
  `web/lib/docs-content.generated.ts`.
- `web/app/[locale]/docs/page.tsx` renders each section as
  `<article className="docs-content" dangerouslySetInnerHTML={{ __html: section.html }} />`.
  No MDX runtime, no JSX compilation at request time, just static HTML
  injection.
- `web/app/globals.css` adds a `@layer components` block targeting
  `.docs-content h1..h4, p, a, ul, ol, li, blockquote, code, pre, table,
  thead, th, td, hr, strong`. Same shadcn-themed look that the dropped
  `mdx-components.tsx` provided, applied via CSS instead of React
  component overrides.
- `web/components/docs/mdx-components.tsx` removed.

We lose MDX features (JSX inside markdown), but the docs are pure
markdown anyway. `docs/` remains the SoT; the marketing site renders
identical content with no eval and no fs at runtime.
This commit is contained in:
YeonGyu-Kim
2026-05-08 16:57:28 +09:00
parent 222f5d07df
commit 9848803f4f
6 changed files with 74 additions and 396 deletions
+4 -6
View File
@@ -1,16 +1,14 @@
import { getTranslations } from "next-intl/server"
import { MDXRemote } from "next-mdx-remote/rsc"
import { DocsShell } from "@/components/docs/docs-shell"
import { mdxComponents } from "@/components/docs/mdx-components"
import { DOC_SECTIONS } from "@/lib/docs-sections"
import { loadDocSource } from "@/lib/docs-source"
export default async function DocsPage() {
const t = await getTranslations("docs")
const sectionsWithSource = DOC_SECTIONS.map((section) => ({
const sectionsWithHtml = DOC_SECTIONS.map((section) => ({
...section,
source: loadDocSource(section.file),
html: loadDocSource(section.file),
}))
return (
@@ -19,9 +17,9 @@ export default async function DocsPage() {
searchPlaceholder={t("searchPlaceholder")}
sections={DOC_SECTIONS.map((s) => ({ id: s.id, title: s.title }))}
>
{sectionsWithSource.map((section) => (
{sectionsWithHtml.map((section) => (
<section key={section.id} id={section.id} className="scroll-mt-24">
<MDXRemote source={section.source} components={mdxComponents} />
<article className="docs-content" dangerouslySetInnerHTML={{ __html: section.html }} />
</section>
))}
</DocsShell>
+60
View File
@@ -232,3 +232,63 @@
text-shadow: 0 0 8px rgba(0, 212, 255, 0.3);
}
}
@layer components {
.docs-content h1 {
@apply mt-8 mb-4 scroll-mt-24 text-4xl font-bold tracking-tight first:mt-0;
}
.docs-content h2 {
@apply mt-12 mb-4 scroll-mt-24 text-2xl font-semibold tracking-tight;
}
.docs-content h3 {
@apply mt-8 mb-3 scroll-mt-24 text-xl font-semibold tracking-tight;
}
.docs-content h4 {
@apply mt-6 mb-2 scroll-mt-24 text-lg font-semibold tracking-tight;
}
.docs-content p {
@apply text-muted-foreground my-4 leading-7;
}
.docs-content a {
@apply text-primary font-medium underline underline-offset-4;
}
.docs-content ul {
@apply text-muted-foreground my-4 ml-6 list-disc space-y-1;
}
.docs-content ol {
@apply text-muted-foreground my-4 ml-6 list-decimal space-y-1;
}
.docs-content li {
@apply leading-7;
}
.docs-content blockquote {
@apply border-primary/40 my-6 border-l-4 pl-4 italic;
}
.docs-content code:not(pre code) {
@apply bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-sm;
}
.docs-content pre {
@apply border-border/50 my-4 overflow-x-auto rounded-lg border bg-[#1e1e2e] p-4 font-mono text-sm text-[#cdd6f4] shadow-sm;
}
.docs-content pre code {
@apply bg-transparent p-0 text-inherit;
}
.docs-content table {
@apply border-border my-6 w-full border-collapse border text-sm;
}
.docs-content thead {
@apply bg-muted;
}
.docs-content th {
@apply border-border border px-3 py-2 text-left font-semibold;
}
.docs-content td {
@apply border-border text-muted-foreground border px-3 py-2;
}
.docs-content hr {
@apply border-border my-8;
}
.docs-content strong {
@apply text-foreground font-semibold;
}
}