文章目录
我们来详细分解「支持 Markdown 博客内容」这一部分的实现,适用于使用 Next.js 搭建静态博客网站的场景。
为什么使用 Markdown?
Markdown 是一种轻量级标记语言,书写简单,格式清晰,非常适合写博客文章。我们可以:
- 在本地的
posts/
文件夹中创建.md
文件 - 使用
gray-matter
提取文章的元信息(标题、日期、标签等) - 使用
remark
、remark-html
把 Markdown 转为 HTML 供 React 渲染
项目结构示例
my-blog/
├─ pages/
│ ├─ index.tsx // 博客首页
│ ├─ posts/
│ └─ [slug].tsx // 博客详情页(动态路由)
├─ posts/
│ ├─ hello-world.md // Markdown 文件
│ └─ another-post.md
├─ lib/
│ └─ posts.ts // Markdown 处理工具函数
├─ public/
├─ styles/
├─ ...
安装依赖
npm install gray-matter remark remark-html
创建 Markdown 文件
在项目根目录下新建一个 posts/
文件夹,创建文件 hello-world.md
:
---
title: "Hello World"
date: "2025-05-26"
tags: ["nextjs", "markdown"]
---
This is my **first** blog post using Markdown!
创建工具函数(lib/posts.ts)
这个文件负责读取 posts
文件夹中的 Markdown 文件,并解析元信息与内容。
// lib/posts.ts
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { remark } from 'remark'
import html from 'remark-html'
const postsDirectory = path.join(process.cwd(), 'posts')
export function getSortedPostsData() {
const fileNames = fs.readdirSync(postsDirectory)
const allPostsData = fileNames.map((fileName) => {
const slug = fileName.replace(/\.md$/, '')
const fullPath = path.join(postsDirectory, fileName)
const fileContents = fs.readFileSync(fullPath, 'utf8')
const matterResult = matter(fileContents)
return {
slug,
...(matterResult.data as { date: string; title: string; tags?: string[] }),
}
})
return allPostsData.sort((a, b) => (a.date < b.date ? 1 : -1))
}
export function getAllPostSlugs() {
const fileNames = fs.readdirSync(postsDirectory)
return fileNames.map((fileName) => ({
params: {
slug: fileName.replace(/\.md$/, ''),
},
}))
}
export async function getPostData(slug: string) {
const fullPath = path.join(postsDirectory, `${slug}.md`)
const fileContents = fs.readFileSync(fullPath, 'utf8')
const matterResult = matter(fileContents)
const processedContent = await remark().use(html).process(matterResult.content)
const contentHtml = processedContent.toString()
return {
slug,
contentHtml,
...(matterResult.data as { title: string; date: string }),
}
}
首页展示文章列表(pages/index.tsx)
import { GetStaticProps } from 'next'
import Link from 'next/link'
import { getSortedPostsData } from '@/lib/posts'
export default function Home({ allPostsData }: { allPostsData: any[] }) {
return (
<div className="max-w-2xl mx-auto py-10">
<h1 className="text-3xl font-bold mb-4">My Blog</h1>
<ul>
{allPostsData.map(({ slug, date, title }) => (
<li key={slug} className="mb-4">
<Link href={`/posts/${slug}`} className="text-xl text-blue-600 hover:underline">
{title}
</Link>
<div className="text-sm text-gray-500">{date}</div>
</li>
))}
</ul>
</div>
)
}
export const getStaticProps: GetStaticProps = async () => {
const allPostsData = getSortedPostsData()
return {
props: {
allPostsData,
},
}
}
详情页展示文章内容(pages/posts/[slug].tsx)
import { GetStaticPaths, GetStaticProps } from 'next'
import { getAllPostSlugs, getPostData } from '@/lib/posts'
export default function Post({ postData }: { postData: any }) {
return (
<div className="max-w-2xl mx-auto py-10">
<h1 className="text-3xl font-bold mb-2">{postData.title}</h1>
<div className="text-sm text-gray-500 mb-6">{postData.date}</div>
<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
</div>
)
}
export const getStaticPaths: GetStaticPaths = async () => {
const paths = getAllPostSlugs()
return {
paths,
fallback: false,
}
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const postData = await getPostData(params?.slug as string)
return {
props: {
postData,
},
}
}
成果展示
- 访问
/
:可以看到所有 Markdown 博客列表。 - 点击文章链接:跳转到对应详情页,内容是从
.md
文件中读取并渲染成 HTML 的。
可选优化
- 支持 代码高亮:使用
rehype-highlight
或shiki
- 使用 MDX 替代 Markdown,可以在文章中嵌入 React 组件
- SEO 优化:用
<Head>
设置标题、meta 信息 - 增加标签/分类系统,即接入标签过滤
- 支持分页
- 支持自定义文章封面图
- 支持搜索功能
以 SEO 优化为例
在 WHAT - SEO(搜索引擎优化) 中我们详细介绍过 SEO 相关内容。
在博客站点搭建中,SEO 是非常重要的一个特性。
下面我会详细讲解如何在 Next.js 博客项目中使用 <Head>
做 SEO 优化,包括:
- 为什么要做 SEO
- Next.js 中的
<Head>
是什么 - 如何在首页和文章详情页中设置标题、描述、关键词等 meta 信息
- 进阶:Open Graph(社交媒体分享优化)
为什么要做 SEO 优化?
SEO(Search Engine Optimization)是为了让你的博客页面更容易被搜索引擎(如百度、Google)收录并排名更高,从而获得更多自然流量。
一个典型的博客页面 SEO 要素包括:
<title>
:页面标题(显示在浏览器标签页 & 搜索结果标题)<meta name="description">
:页面简要描述<meta name="keywords">
:关键词(虽然现在权重低)- Open Graph 标签:用于社交媒体(微信、微博、Twitter、Facebook)分享时展示标题、描述、封面图等
Next.js 中的 Head
Next.js 提供了一个内置的 next/head
组件,允许你在页面中设置 HTML 的 <head>
内容:
import Head from 'next/head'
<Head>
<title>我的博客标题</title>
<meta name="description" content="博客描述" />
<meta name="keywords" content="nextjs, markdown, 博客" />
</Head>
Next.js 会自动将这些内容注入 <head>
标签。
在首页设置 Head
pages/index.tsx:
import Head from 'next/head'
export default function Home() {
return (
<>
<Head>
<title>我的个人博客 | 主页</title>
<meta name="description" content="欢迎访问我的个人博客,记录技术与生活。" />
<meta name="keywords" content="博客, 技术, 前端, Next.js, Markdown" />
</Head>
<main className="max-w-2xl mx-auto">
<h1 className="text-3xl font-bold mb-4">欢迎来到我的博客</h1>
{/* 博客列表... */}
</main>
</>
)
}
在文章详情页设置动态 title 和 meta
pages/posts/[slug].tsx:
import Head from 'next/head'
import { getPostData } from '@/lib/posts'
export default function Post({ postData }: { postData: any }) {
return (
<>
<Head>
<title>{postData.title} | 我的博客</title>
<meta name="description" content={postData.excerpt || postData.contentHtml.slice(0, 160)} />
<meta name="keywords" content={postData.tags?.join(', ') || '博客, 文章'} />
<meta property="og:title" content={postData.title} />
<meta property="og:description" content={postData.excerpt || postData.contentHtml.slice(0, 160)} />
{/* 可选:添加封面图 */}
{/* <meta property="og:image" content={postData.coverImage} /> */}
</Head>
<article className="max-w-2xl mx-auto">
<h1 className="text-3xl font-bold">{postData.title}</h1>
<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
</article>
</>
)
}
Tips:
postData.excerpt
是你可以在 Markdown frontmatter 中添加的字段,也可以程序生成前 160 个字。og:title
/og:description
是 Open Graph 协议,用于社交分享。og:image
可以配合文章封面图使用。
示例 Markdown 文件(添加 meta)
---
title: "使用 Markdown 搭建博客"
date: "2025-05-26"
tags: ["markdown", "博客", "nextjs"]
excerpt: "本篇文章介绍如何用 Next.js 和 Markdown 搭建一个静态博客站点。"
coverImage: "/images/blog-cover.png"
---
正文内容...
建议封装 Head 组件(可复用)
// components/SeoHead.tsx
import Head from 'next/head'
type SeoHeadProps = {
title: string
description?: string
keywords?: string[]
image?: string
}
export function SeoHead({ title, description, keywords, image }: SeoHeadProps) {
return (
<Head>
<title>{title}</title>
{description && <meta name="description" content={description} />}
{keywords && <meta name="keywords" content={keywords.join(', ')} />}
{title && <meta property="og:title" content={title} />}
{description && <meta property="og:description" content={description} />}
{image && <meta property="og:image" content={image} />}
</Head>
)
}
在页面中这样使用:
<SeoHead
title={`${postData.title} | 我的博客`}
description={postData.excerpt}
keywords={postData.tags}
image={postData.coverImage}
/>
SEO 检查工具推荐
另外,还可以进一步:
- 自动生成
sitemap.xml
和robots.txt
- 用
next-sitemap
插件管理 SEO 文件 - 设置全站默认 meta 信息