博客系统
创建和管理多语言博客内容
博客系统
Sistine Starter 包含一个强大的博客系统,支持 MDX、多语言、分类和作者管理。
系统架构
目录结构
app/[locale]/(marketing)/blog/
├── page.tsx # 博客列表页
├── [slug]/
│ └── page.tsx # 博客文章页
│
content/blog/
├── article-1/
│ ├── en.mdx # 英文版本
│ ├── zh.mdx # 中文版本
│ └── thumbnail.jpeg # 文章缩略图
├── article-2/
│ ├── en.mdx
│ ├── zh.mdx
│ └── thumbnail.jpeg
└── [...更多文章]
lib/
├── blog.ts # 博客加载和处理逻辑
└── blog-manifest.generated.ts # 自动生成的博客清单
创建博客文章
1. 文件结构
在 content/blog/
下创建文件夹:
content/blog/my-first-article/
├── en.mdx # 英文内容
├── zh.mdx # 中文内容
└── thumbnail.jpeg # 缩略图 (推荐 1200×630px)
2. Frontmatter 格式
en.mdx:
export const blog = {
author: { name: "Your Name", src: "/avatar.jpeg" },
date: "2025-01-15",
title: "How to Build AI SaaS Products",
description: "Learn how to build production-ready AI SaaS applications using Sistine Starter",
image: "https://images.unsplash.com/photo-xxxxx",
};
export const metadata = {
title: blog.title,
description: blog.description,
openGraph: {
images: [blog.image],
},
};
export default (props) => <BlogLayout blog={blog} {...props} />;
# How to Build AI SaaS Products
文章内容...
zh.mdx:
export const blog = {
author: { name: "你的名字", src: "/avatar.jpeg" },
date: "2025-01-15",
title: "如何构建 AI SaaS 产品",
description: "学习如何使用 Sistine Starter 构建生产级 AI SaaS 应用",
image: "https://images.unsplash.com/photo-xxxxx",
};
export const metadata = {
title: blog.title,
description: blog.description,
openGraph: {
images: [blog.image],
},
};
export default (props) => <BlogLayout blog={blog} {...props} />;
# 如何构建 AI SaaS 产品
文章内容...
3. Frontmatter 字段说明
字段 | 类型 | 说明 | 示例 |
---|---|---|---|
author | Object | 作者信息 | { name: "John", src: "/avatar.jpg" } |
date | string | 发布日期 (YYYY-MM-DD) | "2025-01-15" |
title | string | 文章标题 | "How to Build AI SaaS" |
description | string | 文章摘要 | "Complete guide..." |
image | string | OG 图像 URL | "https://images..." |
category | string (可选) | 分类标签 | "Technology" |
tags | string[] (可选) | 文章标签 | ["AI", "SaaS"] |
博客加载逻辑
lib/blog.ts
interface Blog {
author: { name: string; src: string };
date: string;
title: string;
description: string;
image?: string;
}
// 获取所有博客
export async function getAllBlogs(locale: string = 'en') {
// 动态导入所有博客模块
// 按发布日期降序排列
// 返回博客元数据数组
}
// 获取指定博客
export async function getBlogModule(slug: string, locale: string) {
// 根据 slug 和 locale 获取博客 MDX 模块
// 返回包含 blog、metadata、default 的对象
}
// 获取博客清单
export async function getBlogManifest() {
// 返回所有可用的 blog slug
}
自动生成博客清单
# 运行此脚本自动生成 blog-manifest.generated.ts
pnpm generate:blog
生成的 lib/blog-manifest.generated.ts
包含所有博客的元数据。
博客列表页
app/[locale]/(marketing)/blog/page.tsx:
import { getAllBlogs } from '@/lib/blog';
import { getTranslations, getLocale } from 'next-intl/server';
import { Metadata } from 'next';
export async function generateMetadata({
params
}: {
params: { locale: string }
}): Promise<Metadata> {
const t = await getTranslations({ locale: params.locale, namespace: 'seo' });
return {
title: t('blog.title'),
description: t('blog.description'),
};
}
export default async function BlogPage({
params
}: {
params: { locale: string }
}) {
const locale = await getLocale();
const blogs = await getAllBlogs(locale);
return (
<div className="container py-12">
<h1 className="text-4xl font-bold mb-4">博客</h1>
<p className="text-muted-foreground mb-12">
关于 AI SaaS 开发的最新文章和技巧
</p>
{/* 首页特色文章 (前两篇) */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
{blogs.slice(0, 2).map((blog) => (
<BlogCard key={blog.slug} blog={blog} featured />
))}
</div>
{/* 其他文章 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{blogs.slice(2).map((blog) => (
<BlogCard key={blog.slug} blog={blog} />
))}
</div>
</div>
);
}
博客文章页
app/[locale]/(marketing)/blog/[slug]/page.tsx:
import { getBlogModule, getBlogManifest } from '@/lib/blog';
import { getLocale } from 'next-intl/server';
export async function generateStaticParams() {
const manifest = await getBlogManifest();
// 为每个 slug 和 locale 组合生成路由
return manifest.flatMap(({ slug }) => [
{ locale: 'en', slug },
{ locale: 'zh', slug },
]);
}
export async function generateMetadata({
params
}: {
params: { slug: string; locale: string }
}) {
const blog = await getBlogModule(params.slug, params.locale);
return {
title: `${blog.title} | Sistine Starter`,
description: blog.description,
openGraph: {
images: [blog.image],
},
};
}
export default async function BlogPostPage({
params
}: {
params: { slug: string; locale: string }
}) {
const blog = await getBlogModule(params.slug, params.locale);
const Content = blog.default;
return (
<article className="container py-12 max-w-3xl">
{/* 文章头部 */}
<header className="mb-8">
<h1 className="text-5xl font-bold mb-4">{blog.title}</h1>
<div className="flex items-center gap-4 text-muted-foreground">
<img
src={blog.author.src}
alt={blog.author.name}
className="w-10 h-10 rounded-full"
/>
<div>
<p className="font-semibold text-foreground">{blog.author.name}</p>
<time dateTime={blog.date}>
{new Date(blog.date).toLocaleDateString(params.locale)}
</time>
</div>
</div>
</header>
{/* 文章缩略图 */}
{blog.image && (
<img
src={blog.image}
alt={blog.title}
className="w-full h-96 object-cover rounded-lg mb-8"
/>
)}
{/* 文章内容 */}
<div className="prose dark:prose-invert max-w-none">
<Content />
</div>
</article>
);
}
MDX 组件和特性
1. 代码高亮
\`\`\`typescript
export const blog = {
title: "My Article",
date: "2025-01-15",
};
\`\`\`
2. 表格
| 功能 | 说明 |
|------|------|
| AI对话 | 实时流式对话 |
| 图像生成 | 图生图功能 |
| 视频生成 | 文本或图像生成视频 |
3. 自定义组件
<Callout type="info">
这是一个信息提示框
</Callout>
<Callout type="warning">
这是一个警告提示框
</Callout>
最佳实践
1. 文章标题
✅ 推荐:
- 清晰、具体
- 包含关键词
- 50-60 字符
❌ 避免:
- 太长或太短
- 含糊其辞
- 全大写
2. 发布日期格式
始终使用 ISO 8601 格式 (YYYY-MM-DD):
date: "2025-01-15" // ✅ 正确
date: "Jan 15, 2025" // ❌ 错误
3. 作者头像
- 尺寸: 至少 200×200px
- 格式: JPG, PNG 或 WebP
- 路径: 放在
public/
目录下
4. 缩略图
- 尺寸: 1200×630px (OG 标准)
- 文件大小: < 500KB
- 格式: JPG 或 PNG
- 命名: 使用描述性名称
多语言文章
管理翻译
content/blog/my-article/
├── en.mdx # 英文版本
├── zh.mdx # 中文版本
└── thumbnail.jpeg # 共用缩略图
自动语言选择
博客系统会根据 URL locale 参数自动选择对应语言:
/en/blog/my-article
→ 加载en.mdx
/zh/blog/my-article
→ 加载zh.mdx
语言切换链接
export function LanguageSwitcher({ slug, locale }) {
const otherLocale = locale === 'en' ? 'zh' : 'en';
return (
<a href={`/${otherLocale}/blog/${slug}`}>
{otherLocale === 'en' ? 'English' : '中文'}
</a>
);
}
常见问题
1. 文章不显示
原因:
- 文件夹名称与 slug 不匹配
- 缺少 frontmatter
- 博客清单未更新
解决:
# 重新生成博客清单
pnpm generate:blog
2. 缩略图未显示
原因:
- 图片格式不支持
- 路径错误
- 文件不存在
解决:
- 使用支持的格式 (JPG, PNG, WebP)
- 验证文件位置:
content/blog/article-name/thumbnail.jpeg
3. 多语言内容不同步
原因:
- 忘记创建翻译文件
- Frontmatter 字段不一致
解决:
# 检查文件是否齐全
ls content/blog/my-article/
# 应输出: en.mdx zh.mdx thumbnail.jpeg
部署清单
- 所有文章都有 en.mdx 和 zh.mdx
- Frontmatter 字段完整且格式正确
- 文章日期使用 YYYY-MM-DD 格式
- 缩略图大小为 1200×630px
- 作者头像大小 >= 200×200px
- 运行
pnpm generate:blog
生成清单 - 在本地预览验证所有文章正常显示
- 检查 SEO 元数据正确生成