博客
ShipSaaS 内置一个基于 Content Collections 构建的博客系统。
博客系统架构
Section titled “博客系统架构”博客系统使用 Content Collections 构建,在项目根目录的 content-collections.ts 中定义集合配置。
content
blog
hello-world.md
getting-started.md
deploy-to-production.md
src
routes/blog
index.tsx
$slug.tsx
components/blog
blog-card.tsx
blog-grid.tsx
blog-pagination.tsx
lib
blog.ts
content-collections.ts
博客系统在 content-collections.ts 文件中使用 defineCollection 定义数据集合:
content-collections.ts
import { defineCollection, defineConfig } from '@content-collections/core';import { z } from 'zod';
const blog = defineCollection({ name: 'blog', directory: 'content/blog', include: '**/*.md', schema: z.object({ title: z.string(), description: z.string(), date: z.string(), category: z.string(), content: z.string(), image: z.url(), }), transform: (doc) => ({ ...doc, slug: doc._meta.path, }),});
export default defineConfig({ collections: [blog],});然后在 src/lib/blog.ts 中使用 content-collections 提供的数据进行查询:
src/lib/blog.ts
import { allBlogs } from 'content-collections';import type { Blog } from 'content-collections';
export type BlogPost = Blog & { slug: string };
export function getSortedPosts(): BlogPost[] { return [...(allBlogs as BlogPost[])].sort( (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() );}
export function getPostBySlug(slug: string): BlogPost | undefined { return (allBlogs as BlogPost[]).find((p) => p.slug === slug);}
export function getPaginatedPosts(page: number) { const sorted = getSortedPosts(); // ... pagination logic}创建博客内容
Section titled “创建博客内容”添加新博客文章
Section titled “添加新博客文章”在 content/blog 目录中创建新的 .md 文件:
content/blog/my-first-post.md
---title: My First Blog Postdescription: This is a brief description of my first blog post.date: 2026-01-15category: Tutorialimage: https://example.com/images/blog/my-first-post.jpg---
# Introduction
This is my first blog post. You can use **Markdown** here.
## Section 1
Some content here...
## Section 2
More content here...Frontmatter 字段说明
Section titled “Frontmatter 字段说明”| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| title | string | ✅ | 文章标题 |
| description | string | ✅ | 文章摘要描述 |
| date | string | ✅ | 发布日期(格式:YYYY-MM-DD) |
| category | string | ✅ | 文章分类(单个分类) |
| image | URL | ✅ | 封面图片 URL |
ShipSaaS 的博客使用 .md Markdown 文件格式,每篇文章只有一个 category(字符串),不是多分类数组。
博客使用 TanStack Router 文件路由系统:
src/routes/blog/index.tsx:博客列表页,支持分页src/routes/blog/$slug.tsx:博客文章详情页
src/routes/blog/index.tsx
import { createFileRoute } from '@tanstack/react-router';import { getPaginatedPosts } from '@/lib/blog';
export const Route = createFileRoute('/blog/')({ loader: ({ location }) => { const page = Number(new URLSearchParams(location.search).get('page')) || 1; return getPaginatedPosts(page); }, component: BlogListPage,});src/routes/blog/$slug.tsx
import { createFileRoute, notFound } from '@tanstack/react-router';import { getPostBySlug } from '@/lib/blog';
export const Route = createFileRoute('/blog/$slug')({ loader: async ({ params }) => { const post = getPostBySlug(params.slug); if (!post) throw notFound(); return post; }, component: BlogPostPage,});在 src/config/website.ts 中配置每页显示的文章数量:
src/config/website.ts
export const websiteConfig: WebsiteConfig = { // ...other config blog: { enable: true, paginationSize: 6, }, // ...other config}更改博客文章卡片布局
Section titled “更改博客文章卡片布局”在 src/components/blog/blog-card.tsx 中自定义博客卡片组件:
src/components/blog/blog-card.tsx
import type { BlogPost } from '@/lib/blog';
interface BlogCardProps { post: BlogPost;}
export function BlogCard({ post }: BlogCardProps) { return ( <div className="group flex flex-col border rounded-lg overflow-hidden h-full bg-card shadow-sm hover:shadow-md transition-shadow"> <h3>{post.title}</h3> <p>{post.description}</p> <span>{post.category}</span> <time>{post.date}</time> {/* ... rest of the component */} </div> );}自定义博客文章数据结构
Section titled “自定义博客文章数据结构”要向博客文章添加新字段:
- 在
content-collections.ts中修改 schema - 更新组件以显示新字段
示例:添加“精选”字段
content-collections.ts
const blog = defineCollection({ name: 'blog', directory: 'content/blog', include: '**/*.md', schema: z.object({ title: z.string(), description: z.string(), date: z.string(), category: z.string(), content: z.string(), image: z.url(), // Add new field featured: z.boolean().default(false), }), transform: (doc) => ({ ...doc, slug: doc._meta.path, }),});然后,您可以在博客文章中使用此字段:
content/blog/important-post.md
---title: Important Announcementdescription: Read this important announcementdate: 2026-01-15category: Announcementimage: https://example.com/images/blog/announcement.jpgfeatured: true---
Content here...程序化查询文章
Section titled “程序化查询文章”您可以使用 src/lib/blog.ts 中的工具函数查询文章:
import { getSortedPosts, getPostBySlug, getPaginatedPosts } from '@/lib/blog';
// Get all posts sorted by dateconst allPosts = getSortedPosts();
// Get a specific post by slugconst post = getPostBySlug('hello-world');
// Get paginated postsconst { posts, totalPages, currentPage } = getPaginatedPosts(1);博客系统使用 content-collections 的构建过程:
- 开发:在开发过程中 content-collections 会监视
content/目录的变化并自动重新生成 - 构建:Vite 构建时自动处理 content-collections,无需额外命令
- 生成的文件:
.content-collections目录包含生成的 TypeScript 文件
content-collections 与 Vite 集成,开发和构建过程完全自动化,无需手动运行额外命令。
- 使用高质量图片:为博客文章使用适当大小和优化的图片
- 一致的分类:在文章列表中保持一致的分类名称
- 清晰的元数据:编写清晰的标题和描述以提高 SEO 效果
- 结构化内容:在博客文章内容中使用适当的标题和段落
- 日期格式:使用
YYYY-MM-DD格式的日期字符串 - 使用 Zod 验证:使用 Zod 进行类型安全的内容验证
现在您了解了如何在 ShipSaaS 中使用博客系统,您可能想要探索这些相关功能: