fix: fix empty post bodies — stop double-compiling MDX
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import { MDXRemote } from 'next-mdx-remote/rsc'
|
import { MDXRemote } from 'next-mdx-remote/rsc'
|
||||||
|
import { useMDXComponents } from '@/mdx-components'
|
||||||
|
import remarkMath from 'remark-math'
|
||||||
|
import remarkGfm from 'remark-gfm'
|
||||||
|
import rehypePrettyCode from 'rehype-pretty-code'
|
||||||
|
import rehypeKatex from 'rehype-katex'
|
||||||
import { getPosts, getPost, getReadingTime } from '@/lib/posts'
|
import { getPosts, getPost, getReadingTime } from '@/lib/posts'
|
||||||
import { TableOfContents } from '@/components/blog/TableOfContents'
|
import { TableOfContents } from '@/components/blog/TableOfContents'
|
||||||
import { ScrollToTop } from '@/components/ui/ScrollToTop'
|
import { ScrollToTop } from '@/components/ui/ScrollToTop'
|
||||||
@@ -43,7 +48,19 @@ export default async function PostPage({ params }: { params: Promise<{ slug: str
|
|||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
<div className="prose prose-lg max-w-none">
|
<div className="prose prose-lg max-w-none">
|
||||||
<MDXRemote source={post.source} />
|
<MDXRemote
|
||||||
|
source={post.source}
|
||||||
|
components={useMDXComponents({})}
|
||||||
|
options={{
|
||||||
|
mdxOptions: {
|
||||||
|
remarkPlugins: [remarkMath, remarkGfm],
|
||||||
|
rehypePlugins: [
|
||||||
|
[rehypePrettyCode, { theme: 'github-dark' }],
|
||||||
|
rehypeKatex,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<aside className="hidden lg:block">
|
<aside className="hidden lg:block">
|
||||||
|
|||||||
61
lib/posts.ts
61
lib/posts.ts
@@ -1,13 +1,7 @@
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import matter from 'gray-matter'
|
import matter from 'gray-matter'
|
||||||
import { compileMDX } from 'next-mdx-remote/rsc'
|
|
||||||
import { cache } from 'react'
|
import { cache } from 'react'
|
||||||
import { useMDXComponents as getMDXComponents } from '@/mdx-components'
|
|
||||||
import remarkMath from 'remark-math'
|
|
||||||
import remarkGfm from 'remark-gfm'
|
|
||||||
import rehypeKatex from 'rehype-katex'
|
|
||||||
import rehypePrettyCode from 'rehype-pretty-code'
|
|
||||||
|
|
||||||
const postsDirectory = path.join(process.cwd(), 'content/posts')
|
const postsDirectory = path.join(process.cwd(), 'content/posts')
|
||||||
|
|
||||||
@@ -16,15 +10,19 @@ export interface PostMeta {
|
|||||||
title: string
|
title: string
|
||||||
date: string
|
date: string
|
||||||
excerpt: string
|
excerpt: string
|
||||||
|
tags: string[]
|
||||||
|
author: string | null
|
||||||
|
coverImage: string | null
|
||||||
|
readingTime: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Post extends PostMeta {
|
export interface Post extends PostMeta {
|
||||||
source: string
|
source: string // raw MDX string, NOT compiled
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMdxFiles = cache(async () => {
|
const getMdxFiles = cache(async () => {
|
||||||
try {
|
try {
|
||||||
const files = await fs.readdir(postsDirectory)
|
const files = await fs.promises.readdir(postsDirectory)
|
||||||
return files.filter((f) => f.endsWith('.mdx') || f.endsWith('.md'))
|
return files.filter((f) => f.endsWith('.mdx') || f.endsWith('.md'))
|
||||||
} catch {
|
} catch {
|
||||||
return []
|
return []
|
||||||
@@ -36,14 +34,23 @@ export const getPosts = cache(async (): Promise<PostMeta[]> => {
|
|||||||
return Promise.all(
|
return Promise.all(
|
||||||
files.map(async (file) => {
|
files.map(async (file) => {
|
||||||
const filePath = path.join(postsDirectory, file)
|
const filePath = path.join(postsDirectory, file)
|
||||||
const raw = await fs.readFile(filePath, 'utf8')
|
const raw = await fs.promises.readFile(filePath, 'utf-8')
|
||||||
const { data } = matter(raw)
|
const { data } = matter(raw)
|
||||||
|
const slug = file.replace(/\.(mdx|md)$/, '')
|
||||||
|
// Compute reading time from content (everything after frontmatter)
|
||||||
|
const content = raw.split(/---\n*\n*/).slice(2).join('\n')
|
||||||
|
const readingTime = Math.max(1, Math.ceil(content.split(/\s+/).length / 200))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slug: file.replace(/\.(mdx|md)$/, ''),
|
slug,
|
||||||
title: data.title ?? file,
|
title: data.title ?? slug,
|
||||||
date: data.date ?? 'Unknown',
|
date: data.date ?? 'Unknown',
|
||||||
excerpt: data.excerpt ?? '',
|
excerpt: data.excerpt ?? '',
|
||||||
} as PostMeta
|
tags: data.tags ?? [],
|
||||||
|
author: data.author ?? null,
|
||||||
|
coverImage: data.coverImage ?? null,
|
||||||
|
readingTime,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
).then((posts) =>
|
).then((posts) =>
|
||||||
posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||||
@@ -56,32 +63,22 @@ export const getPost = async (slug: string): Promise<Post | null> => {
|
|||||||
if (!file) return null
|
if (!file) return null
|
||||||
|
|
||||||
const filePath = path.join(postsDirectory, file)
|
const filePath = path.join(postsDirectory, file)
|
||||||
const raw = await fs.readFile(filePath, 'utf8')
|
const raw = await fs.promises.readFile(filePath, 'utf8')
|
||||||
const { data, content } = matter(raw)
|
const { data, content } = matter(raw)
|
||||||
const components = getMDXComponents({})
|
|
||||||
|
|
||||||
const { content: compiledContent } = await compileMDX({
|
const readingTime = Math.max(1, Math.ceil(content.split(/\s+/).length / 200))
|
||||||
source: content,
|
|
||||||
components,
|
|
||||||
options: {
|
|
||||||
parseFrontmatter: true,
|
|
||||||
mdxOptions: {
|
|
||||||
remarkPlugins: [remarkMath, remarkGfm],
|
|
||||||
rehypePlugins: [
|
|
||||||
[rehypePrettyCode, { theme: 'github-dark' }],
|
|
||||||
rehypeKatex,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slug,
|
slug,
|
||||||
title: data.title ?? file,
|
title: data.title ?? slug,
|
||||||
date: data.date ?? 'Unknown',
|
date: data.date ?? 'Unknown',
|
||||||
excerpt: data.excerpt ?? '',
|
excerpt: data.excerpt ?? '',
|
||||||
source: compiledContent,
|
tags: data.tags ?? [],
|
||||||
} as unknown as Post
|
author: data.author ?? null,
|
||||||
|
coverImage: data.coverImage ?? null,
|
||||||
|
source: content,
|
||||||
|
readingTime,
|
||||||
|
} satisfies Post
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getReadingTime = (content: string): number => {
|
export const getReadingTime = (content: string): number => {
|
||||||
|
|||||||
Reference in New Issue
Block a user