Loading articles...
个人技术博客是开发者展示技术深度和记录学习路径的重要载体。选择 Next.js 和 Supabase 的组合有几个关键原因:
我们采用 feature-first 的目录组织方式:
frontend/
src/
app/ # 路由与页面
components/ # 通用 UI 组件
features/ # 业务功能模块
lib/ # 工具与数据访问
config/ # 环境变量与路径
博客的核心数据模型包含四张表:
| 表名 | 用途 |
|---|---|
posts |
文章内容与元数据 |
categories |
文章分类 |
tags |
标签 |
post_tags |
文章与标签多对多关联 |
CREATE TABLE public.posts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
title text NOT NULL,
slug text NOT NULL UNIQUE,
excerpt text,
content text NOT NULL,
status text NOT NULL DEFAULT 'draft'
CHECK (status IN ('draft', 'published', 'archived')),
published_at timestamptz,
category_id uuid REFERENCES public.categories(id),
reading_time integer,
search_vector tsvector,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
Supabase 的 Row Level Security 让我们可以在数据库层面控制访问权限。V1 博客只开放匿名读取已发布内容:
CREATE POLICY "公开读取已发布文章" ON public.posts
FOR SELECT TO anon
USING (status = 'published' AND published_at <= now());
使用 unified + remark-gfm + rehype-highlight 组合处理 Markdown:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkGfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypeHighlight from "rehype-highlight";
import rehypeStringify from "rehype-stringify";
export async function parseMarkdown(content: string): Promise<string> {
const result = await unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype)
.use(rehypeHighlight, { detect: true })
.use(rehypeStringify)
.process(content);
return String(result);
}
Next.js + Supabase 是构建个人博客的理想组合。Server Components 让数据获取变得简洁,Supabase 的 RLS 提供了安全的数据访问控制。在下一篇文章中,我们将深入 TypeScript 的类型系统。
加载评论…