diff --git a/apps/website/app/blog/[slug]/page.tsx b/apps/website/app/blog/[slug]/page.tsx index 9ae6b2983..ff37cd687 100644 --- a/apps/website/app/blog/[slug]/page.tsx +++ b/apps/website/app/blog/[slug]/page.tsx @@ -2,7 +2,6 @@ import type { Metadata } from "next"; import Image from "next/image"; import Link from "next/link"; import { notFound } from "next/navigation"; -import { MDXRemote } from "next-mdx-remote/rsc"; import { mdxComponents } from "@/components/blogComponents"; import { getAllPosts, getPostBySlug } from "@/lib/blogUtils"; import type { BlogParams } from "@/lib/types"; @@ -45,11 +44,23 @@ function formatDate(dateString: string | null) { }); } +async function loadMdxContent({ slug }: { slug: string }) { + try { + const mod = await import(`@/content/blog/${slug}.mdx`); + return mod.default as React.ComponentType<{ components?: Record }>; + } catch { + return null; + } +} + export default async function BlogPostPage({ params }: { params: BlogParams }) { const { slug } = await params; const post = getPostBySlug({ slug }); if (!post) notFound(); + const Content = await loadMdxContent({ slug }); + if (!Content) notFound(); + return (
@@ -109,7 +120,7 @@ export default async function BlogPostPage({ params }: { params: BlogParams }) {
- +
diff --git a/apps/website/mdx-components.tsx b/apps/website/mdx-components.tsx new file mode 100644 index 000000000..ec2e412c1 --- /dev/null +++ b/apps/website/mdx-components.tsx @@ -0,0 +1,6 @@ +import type { MDXComponents } from "mdx/types"; +import { mdxComponents } from "@/components/blogComponents"; + +export function useMDXComponents(components: MDXComponents): MDXComponents { + return { ...mdxComponents, ...components }; +} diff --git a/apps/website/next.config.mjs b/apps/website/next.config.mjs index 320c73586..09dfd8978 100644 --- a/apps/website/next.config.mjs +++ b/apps/website/next.config.mjs @@ -1,5 +1,10 @@ +import createMDX from "@next/mdx"; + +const isProd = process.env.NODE_ENV === "production"; + /** @type {import('next').NextConfig} */ const nextConfig = { + pageExtensions: ["ts", "tsx", "md", "mdx"], allowedDevOrigins: ["*.ngrok-free.dev"], experimental: { optimizePackageImports: ["framer-motion", "motion", "gsap", "@gsap/react"], @@ -11,10 +16,9 @@ const nextConfig = { // formats: ["image/avif", "image/webp"], }, async headers() { + if (!isProd) return []; + return [ - // Next.js chunks include a content hash in their filename, so they are - // safe to cache indefinitely. Vercel sets this automatically on its CDN, - // but the explicit header also covers self-hosted deployments. { source: "/_next/static/(.*)", headers: [ @@ -24,8 +28,6 @@ const nextConfig = { }, ], }, - // Lottie JSON files are large (1.4–2.6 MB each) and change infrequently. - // A 1-year cache means repeat visitors skip the expensive re-download. { source: "/animation/(.*)", headers: [ @@ -35,8 +37,6 @@ const nextConfig = { }, ], }, - // Public images: 7-day TTL with a 1-day stale-while-revalidate window - // so content updates eventually propagate without blocking visitors. { source: "/images/(.*)", headers: [ @@ -76,4 +76,10 @@ const nextConfig = { }, }; -export default nextConfig; +const withMDX = createMDX({ + options: { + remarkPlugins: ["remark-frontmatter"], + }, +}); + +export default withMDX(nextConfig); diff --git a/apps/website/package.json b/apps/website/package.json index c32081a24..66cae08e1 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -10,6 +10,11 @@ }, "dependencies": { "@gsap/react": "^2.1.2", + "@mdx-js/loader": "^3.1.1", + "@mdx-js/mdx": "^3.1.1", + "@mdx-js/react": "^3.1.1", + "@next/mdx": "^16.2.4", + "@types/react": "^19.2.14", "@vercel/analytics": "^2.0.1", "framer-motion": "^12.38.0", "gray-matter": "^4.0.3", @@ -19,12 +24,11 @@ "matter-js": "^0.20.0", "motion": "^12.38.0", "next": "16.2.4", - "next-mdx-remote": "^6.0.0", "ngrok": "^5.0.0-beta.2", "react": "19.2.4", "react-dom": "19.2.4", "react-syntax-highlighter": "^16.1.1", - "@types/react": "^19.2.14" + "remark-frontmatter": "^5.0.0" }, "devDependencies": { "@tailwindcss/postcss": "^4.2.2", diff --git a/bun.lock b/bun.lock index c8c098e58..c96c7e98f 100644 --- a/bun.lock +++ b/bun.lock @@ -107,6 +107,10 @@ "version": "0.1.0", "dependencies": { "@gsap/react": "^2.1.2", + "@mdx-js/loader": "^3.1.1", + "@mdx-js/mdx": "^3.1.1", + "@mdx-js/react": "^3.1.1", + "@next/mdx": "^16.2.4", "@types/react": "^19.2.14", "@vercel/analytics": "^2.0.1", "framer-motion": "^12.38.0", @@ -117,11 +121,11 @@ "matter-js": "^0.20.0", "motion": "^12.38.0", "next": "16.2.4", - "next-mdx-remote": "^6.0.0", "ngrok": "^5.0.0-beta.2", "react": "19.2.4", "react-dom": "19.2.4", "react-syntax-highlighter": "^16.1.1", + "remark-frontmatter": "^5.0.0", }, "devDependencies": { "@tailwindcss/postcss": "^4.2.2", @@ -1248,6 +1252,8 @@ "@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="], + "@mdx-js/loader": ["@mdx-js/loader@3.1.1", "", { "dependencies": { "@mdx-js/mdx": "^3.0.0", "source-map": "^0.7.0" }, "peerDependencies": { "webpack": ">=5" }, "optionalPeers": ["webpack"] }, "sha512-0TTacJyZ9mDmY+VefuthVshaNIyCGZHJG2fMnGaDttCt8HmjUF7SizlHJpaCDoGnN635nK1wpzfpx/Xx5S4WnQ=="], + "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], @@ -1304,6 +1310,8 @@ "@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.2.1", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-r0epZGo24eT4g08jJlg2OEryBphXqO8aL18oajoTKLzHJ6jVr6P6FI58DLMug04MwD3j8Fj0YK0slyzneKVyzA=="], + "@next/mdx": ["@next/mdx@16.2.4", "", { "dependencies": { "source-map": "^0.7.0" }, "peerDependencies": { "@mdx-js/loader": ">=0.15.0", "@mdx-js/react": ">=0.15.0" }, "optionalPeers": ["@mdx-js/loader", "@mdx-js/react"] }, "sha512-e/3bgla+/oF3vDlndI0eFPa0bnP47HPVA0InsAJi7Jr3DwV8WpEGuOcm/3PdI5/93FfNiBhMVeVHZpm1sFlmJw=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A=="], "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ=="], @@ -4370,8 +4378,6 @@ "next": ["next@16.2.4", "", { "dependencies": { "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.4", "@next/swc-darwin-x64": "16.2.4", "@next/swc-linux-arm64-gnu": "16.2.4", "@next/swc-linux-arm64-musl": "16.2.4", "@next/swc-linux-x64-gnu": "16.2.4", "@next/swc-linux-x64-musl": "16.2.4", "@next/swc-win32-arm64-msvc": "16.2.4", "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q=="], - "next-mdx-remote": ["next-mdx-remote@6.0.0", "", { "dependencies": { "@babel/code-frame": "^7.23.5", "@mdx-js/mdx": "^3.0.1", "@mdx-js/react": "^3.0.1", "unist-util-remove": "^4.0.0", "unist-util-visit": "^5.1.0", "vfile": "^6.0.1", "vfile-matter": "^5.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-cJEpEZlgD6xGjB4jL8BnI8FaYdN9BzZM4NwadPe1YQr7pqoWjg9EBCMv3nXBkuHqMRfv2y33SzUsuyNh9LFAQQ=="], - "next-mdx-remote-client": ["next-mdx-remote-client@1.1.7", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@mdx-js/mdx": "^3.1.1", "@mdx-js/react": "^3.1.1", "remark-mdx-remove-esm": "^1.3.1", "serialize-error": "^13.0.1", "vfile": "^6.0.3", "vfile-matter": "^5.0.1" }, "peerDependencies": { "react": ">= 18.3.0 < 19.0.0", "react-dom": ">= 18.3.0 < 19.0.0" } }, "sha512-12Ap5Z/tFIETMXFSBTH2IFEhJAso7MvOJ5ICyesA4q6FM4vtAcmb+4ZKa4tV1IVQJLBVqOhaEfIESZzdwjmrQQ=="], "ngrok": ["ngrok@5.0.0-beta.2", "", { "dependencies": { "extract-zip": "^2.0.1", "got": "^11.8.5", "lodash.clonedeep": "^4.5.0", "uuid": "^7.0.0 || ^8.0.0", "yaml": "^2.2.2" }, "optionalDependencies": { "hpagent": "^0.1.2" }, "bin": { "ngrok": "bin/ngrok" } }, "sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ=="],