基于 Next.js 16 (App Router) + TypeScript + Tailwind CSS v4 + MDX 的个人博客,对 Typora 笔记完全友好。
- 首页 Hero:渐变背景、视差滚动、打字动画、Canvas 粒子层
- 博客列表:文件树导航 + 分类/标签/文件夹筛选 + 列表/卡片视图切换
- 文章页:嵌套目录、右侧 TOC(与渲染的 heading id 对齐)、面包屑、阅读进度条、返回顶部
- MDX 渲染:代码高亮(
rehype-pretty-code,双主题 Shiki)、数学公式(KaTeX)、GFM - Typora 扩展语法:
==高亮==/H~2~O下标 /mc^2^上标 /:smile:emoji /[toc]文章内目录 /```mermaid流程图 / Typora<img style="zoom:N%"> - LaTeX 论文风阅读主题:右下角一键切换,自动编号标题、Times serif、A4 纸张感,跟随明暗模式
- 暗色模式(
next-themes,系统主题检测) - 多作者(JSON 配置 + 作者页)
- 全站点击涟漪动画
npm install
npm run dev # http://localhost:3000
npm run build # 生产构建
npm run lintapp/
page.tsx # 首页
about/page.tsx # 关于
blog/page.tsx # 博客列表
blog/[...slug]/page.tsx # 文章页(支持嵌套路径)
authors/[id]/page.tsx # 作者页
api/images/[...path]/route.ts # 文章内相对路径图片代理
globals.css # 站点全局样式
latex-theme.css # LaTeX 论文风阅读主题(scoped 在 .theme-latex 下)
components/
Hero/ # 首页 Hero(动画、粒子、打字)
layouts/ # ListLayout / CardLayout
ui/ # shadcn/ui(dialog、button、input)
ClickEffect.tsx # 全站点击动画
ReadingTheme.tsx # 阅读主题 Provider + ArticleBody + 切换按钮
ArticleTOC.tsx # [toc] 渲染出的客户端目录组件
Mermaid.tsx # mermaid 代码块的客户端渲染(懒加载)
MDXComponents.tsx # MDX 元素映射 + 图片路径解析
FileTreeView.tsx # 侧边文件树
TableOfContents.tsx # 文章右侧 TOC
Breadcrumb.tsx, MobileTOC.tsx, BackToTop.tsx, ReadingProgress.tsx
Navigation.tsx, SearchDialog.tsx, ThemeToggle.tsx, ViewSwitcher.tsx
lib/
posts.ts # 扫描 content/posts,构建文件树、加载文章
authors.ts # 读取作者 JSON
mdx.ts # MDX 编译管线 + Typora 兼容预处理 + 自定义 remark 插件
toc.ts # 从 markdown 提取目录
utils.ts # cn() 工具
content/
posts/ # 文章源(Markdown,支持嵌套文件夹)
authors/ # 作者 JSON
scripts/
sync-md-to-mdx.ts # 将散装 .md 同步成文件夹形式
watch-md.ts # 监听 content/posts/**/*.md 自动 sync
types/index.ts # Post / Folder / FileTree 等类型
content/posts/ 下的每个子文件夹都是一个「文章文件夹」,可嵌套。两种放文章的方式:
方式 A:文件夹下直接放 .md
content/posts/
└── Compiler_Principle/ # 一个文件夹 = 一个系列
├── .folder.json # 文件夹元数据(显示名、图标、排序等)
├── index.md # 系列首页 → 路由 /blog/Compiler_Principle
├── Lec1.md # → /blog/Compiler_Principle/Lec1
├── Lec2.md # → /blog/Compiler_Principle/Lec2
└── assets/ # 同目录图片
└── image-xxx.png
方式 B:每篇文章独立文件夹
content/posts/
└── hello-world/
└── index.md # → /blog/hello-world
两种方式可以混用。index.md 的路由等于它所在的文件夹路径。
文件夹/文件名建议用下划线或连字符(
Operating_System/compiler-principle),别用空格 —— URL 里要 encode,分享/复制粘贴容易断。
所有字段都是可选的 —— 缺失字段会在加载时填默认值(参见 lib/posts.ts 的 loadPost),所以 Typora 笔记直接复制过来也能跑。
---
title: "文章标题" # 缺省时用文件名
date: "2026-03-06" # 缺省时用文件 mtime
description: "一句话摘要"
tags: ["标签1", "标签2"]
category: "分类" # 缺省时为「未分类」
author: "zhuzichao" # 对应 content/authors/zhuzichao.json
coverImage: "/images/xxx.jpg" # 可选
draft: false # true 时跳过
---
## 正文从 h2 开始
支持 GFM、KaTeX 公式、`==高亮==`、代码块、相对路径图片。{
"displayName": "编译原理",
"icon": "📚",
"order": 1,
"collapsed": false
}lib/mdx.ts 里的预处理 + 自定义 remark 插件让 Typora 笔记可以原样扔进来:
| 写法 | 渲染结果 | 说明 |
|---|---|---|
==高亮== |
高亮 | 行内 <mark> |
H~2~O |
H2O | 下标。remark-gfm 的 singleTilde |
mc^2^ |
mc2 | 上标 |
:smile: :rocket: |
😄 🚀 | remark-emoji |
[toc](独占一行) |
文章内嵌目录 | ArticleTOC.tsx 客户端扫描已渲染的 h2~h4 生成 |
```mermaid 代码块 |
流程图 | Mermaid.tsx 客户端懒加载 mermaid(~700KB),跟随明暗主题 |
<img style="zoom:50%"> |
等价 width="50%" |
字符串 style 在 React JSX 里会报错,预处理时自动改写 |
<segment, offset>、a < b、count <>0 |
自动转义为 < |
MDX 默认会把这些误当 JSX 标签开头炸掉 |
~~delete~~ |
GFM 标准 | |
$x$ / $$x$$ |
LaTeX 公式 | KaTeX |
- [x] 任务列表、表格、脚注 |
GFM 标准 | 全部支持 |
如果遇到没覆盖的 Typora corner case,规则就在 lib/mdx.ts 的 preprocessMarkdown 和 remarkTyporaInline / remarkMermaid / remarkTocPlaceholder 几个函数里,加一行就行。
文章里的相对路径图片(./assets/xxx.png)由 components/MDXComponents.tsx 改写成 /api/images/<文件夹>/assets/xxx.png,再由 app/api/images/[...path]/route.ts 从 content/posts/ 下读出来返回。图片跟 Markdown 放一起就行,不需要塞进 public/。
如果你习惯先在 Typora 里写散装 .md,scripts/sync-md-to-mdx.ts 会把 Foo.md 挪成 Foo/index.md 形式。
npm run sync:md # 全量同步
npm run sync:md Foo.md # 单文件同步
npm run watch:md # 监听自动同步当前的
lib/posts.ts已经能直接加载文件夹里的散装.md(作为兄弟文件,见方式 A),所以这个脚本对大多数场景可有可无。
文章页右下角有两个浮动按钮:
- 回到顶部(默认主题样式)
- 📰 切换 LaTeX 论文风 ↔ 📄 切换默认主题
LaTeX 主题改编自 Typora 经典的 LaTeX 主题,重写为博客可用的 scoped CSS(app/latex-theme.css),保留:
- Times serif + 中文宋体的字体栈
- 标题自动编号(
1、1.1、1.1.1...) - booktabs 三线表
- 自定义无序/有序列表标记(
–◦/(a)i.) - A4 纸宽 + 阴影 + 居中
切换是局部的(<ArticleBody> 在默认 prose 和 .theme-latex 之间换 className),不会影响导航栏、TOC、首页 Hero 等任何站点其他部分。选择存在 localStorage['reading-theme']。暗色模式 + LaTeX 主题正交可叠加。
content/authors/<id>.json:
{
"name": "朱子超",
"bio": "一句话简介",
"avatar": "/avatars/me.jpg",
"social": {
"github": "username",
"twitter": "username",
"website": "https://example.com"
}
}文章的 author 字段填文件名里的 <id> 即可,自动关联。
| 想改的东西 | 去哪改 |
|---|---|
| 主题色、暗色变量 | app/globals.css 里的 CSS 变量 |
| LaTeX 主题字体/字号/纸张 | app/latex-theme.css 顶部 --latex-* 变量 |
| 导航栏标题/菜单 | components/Navigation.tsx |
| 首页文案、打字动画 | components/Hero/AnimatedHero.tsx |
| 点击动画颜色/粒子数 | components/ClickEffect.tsx |
| 站点元数据 | app/layout.tsx 的 metadata |
| MDX 渲染映射、Typora 语法 | components/MDXComponents.tsx、lib/mdx.ts |
任何支持 Next.js 的平台都行。注意:
/api/images/[...path]是动态路由,不能静态导出(output: 'export')。如果要走 GitHub Pages 纯静态方案,需要先把文章图片复制到public/并调整MDXComponents.tsx的路径改写逻辑。- Vercel 直接 import 仓库即可,无需额外配置。
- Next.js 16 · React 19 · TypeScript 5
- Tailwind CSS v4 · shadcn/ui (dialog/button/input) ·
tw-animate-css - MDX:
next-mdx-remote+remark-gfm+remark-math+remark-emoji+rehype-katex+rehype-slug+rehype-autolink-headings+rehype-pretty-code(Shiki) - 图表:
mermaid(懒加载,仅出现```mermaid块的页面才下载) - 内容:
gray-matter+reading-time+github-slugger - 动画:
framer-motion - 图标:
lucide-react - 主题:
next-themes - 日期:
date-fns