LogoTanStarter 文档
LogoTanStarter 文档
首页模板介绍代码库快速开始环境配置
网站配置
部署

功能集成

Cloudflare数据库身份验证邮件邮件订阅存储支付通知分析聊天框联盟营销

功能定制

元数据页面落地页博客组件用户管理密钥管理

代码库

项目结构代码检查编辑器设置更新代码库
X (Twitter)

博客

学习如何创建、管理和自定义博客文章

TanStarter 内置一个基于 Content Collections 构建的博客系统。

博客系统架构

博客系统使用 Content Collections 构建,在项目根目录的 content-collections.ts 中定义集合配置。

hello-world.md
getting-started.md
deploy-to-production.md
index.tsx
$slug.tsx
blog-card.tsx
blog-grid.tsx
blog-pagination.tsx
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
}

创建博客内容

添加新博客文章

在 content/blog 目录中创建新的 .md 文件:

content/blog/my-first-post.md
---
title: My First Blog Post
description: This is a brief description of my first blog post.
date: 2026-01-15
category: Tutorial
image: 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 字段说明

字段类型必填说明
titlestring✅文章标题
descriptionstring✅文章摘要描述
datestring✅发布日期(格式:YYYY-MM-DD)
categorystring✅文章分类(单个分类)
imageURL✅封面图片 URL

TanStarter 的博客使用 .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
}

更改博客文章卡片布局

在 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>
  );
}

自定义博客文章数据结构

要向博客文章添加新字段:

  1. 在 content-collections.ts 中修改 schema
  2. 更新组件以显示新字段

示例:添加"精选"字段

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 Announcement
description: Read this important announcement
date: 2026-01-15
category: Announcement
image: https://example.com/images/blog/announcement.jpg
featured: true
---

Content here...

程序化查询文章

您可以使用 src/lib/blog.ts 中的工具函数查询文章:

import { getSortedPosts, getPostBySlug, getPaginatedPosts } from '@/lib/blog';

// Get all posts sorted by date
const allPosts = getSortedPosts();

// Get a specific post by slug
const post = getPostBySlug('hello-world');

// Get paginated posts
const { posts, totalPages, currentPage } = getPaginatedPosts(1);

构建过程

博客系统使用 content-collections 的构建过程:

  1. 开发:在开发过程中 content-collections 会监视 content/ 目录的变化并自动重新生成
  2. 构建:Vite 构建时自动处理 content-collections,无需额外命令
  3. 生成的文件:.content-collections 目录包含生成的 TypeScript 文件

content-collections 与 Vite 集成,开发和构建过程完全自动化,无需手动运行额外命令。

最佳实践

  1. 使用高质量图片:为博客文章使用适当大小和优化的图片
  2. 一致的分类:在文章列表中保持一致的分类名称
  3. 清晰的元数据:编写清晰的标题和描述以提高 SEO 效果
  4. 结构化内容:在博客文章内容中使用适当的标题和段落
  5. 日期格式:使用 YYYY-MM-DD 格式的日期字符串
  6. 使用 Zod 验证:使用 Zod 进行类型安全的内容验证

下一步

现在您了解了如何在 TanStarter 中使用博客系统,您可能想要探索这些相关功能:

邮件订阅

配置邮件订阅

网站配置

配置网站设置

自定义页面

自定义页面

部署

部署到 Cloudflare Workers

落地页

学习如何使用内置的组件模块创建美观、响应式的落地页

组件

了解 TanStarter 中可用的组件及如何有效使用

目录

博客系统架构
数据源配置
创建博客内容
添加新博客文章
Frontmatter 字段说明
路由和页面
自定义
配置分页
更改博客文章卡片布局
自定义博客文章数据结构
程序化查询文章
构建过程
最佳实践
下一步