Menu

如何使用 CMS 模块

提醒

在 v3.2.3 - 3.2.4 更新中,NEXTY.DEV 的 CMS 使用 TipTap 重构,获得了更多的特性,内容编辑器的使用体验也更加完美。

支持的特性

NEXTY.DEV 模板提供的 CMS 功能属于所有 SaaS 模板里最完善的,支持的功能非常丰富,而且通过 TipTap 丰富的插件生态你还可以轻松扩展出更多功能。

以下是 NEXTY.DEV 模板已支持的功能:

核心功能

  • 多内容模块支持:默认提供博客(blog)和术语表(glossary)两个模块
  • 统一架构设计:基础组件通用、数据表共用,通过添加少量文件和代码即可创建出新的内容模块
  • 多内容源支持:支持本地 MDX 文件渲染和服务端 CMS 数据渲染,且每个模块可独立配置
  • 阅读统计:支持统计阅读量,提供两种模式:
    • all 模式:统计每次页面加载
    • unique 模式:同一 IP 地址每小时只计数一次
  • 目录导航(TOC):详情页自动生成文章目录,支持桌面端侧边栏和移动端折叠显示
  • 关联文章推荐:详情页支持展示关联文章(以相同 tag 作为判断依据)
  • 封面图支持:支持本地上传、外部链接、CloudFlare R2 选择
  • AI 翻译:支持编辑器内置翻译按钮,支持快速翻译选中内容
  • 灵活配置:工具栏大部分插件支持通过配置开启或关闭

内容管理功能

创建、编辑内容支持:

  • 基础信息
    • 标题(title)、URL 别名(slug)、描述(description)
    • 标签(tags)管理,最多支持 5 个标签
    • 封面图上传(支持本地上传、外部链接、CloudFlare R2 选择)
  • 多语言支持
    • 选择内容语言(支持所有配置的语言)
    • 支持不同语言但相同 slug 的设计
  • 高级功能
    • 状态设置:草稿(draft)、发布(published)、归档(archived)
    • 可见性控制:公开(public)、登录用户(logged_in)、订阅用户(subscribers)
    • 置顶功能:支持将重要内容置顶显示
  • 内容保护:对于需要登录或订阅的内容,系统会自动显示访问限制提示

后台管理功能

  • 数据表格展示
    • 分页浏览所有内容
    • 支持搜索过滤(标题、slug、描述)
    • 支持按状态、语言筛选
  • 标签管理
    • 创建、编辑、删除标签
    • 标签与内容模块关联(每个模块有独立的标签系统)
  • 批量操作
    • 复制内容(duplicate)功能
    • 删除内容
  • 封面图管理
    • 支持配置是否在列表页显示封面图(showCoverInList 配置项)
    • 未上传封面图时自动生成动态 OG 图片

目录结构

components/cms/               // CMS 公共组件
  ├─ PostDataTable.tsx        // 后台列表 & 分页
  ├─ PostEditorClient.tsx     // 创建/编辑入口(客户端组件)
  ├─ PostForm.tsx             // 表单组件
  ├─ PostList.tsx             // 前台无限滚动列表
  ├─ PostCard.tsx             // 前台内容卡片
  ├─ PostListActions.tsx      // 后台操作按钮(编辑、复制、删除)
  ├─ RelatedPosts.tsx         // 关联文章组件
  ├─ ContentRestrictionMessage.tsx  // 内容访问限制提示
  ├─ ImageUpload.tsx          // 图片上传组件(支持 R2)
  ├─ TagInput.tsx             // 标签输入组件
  ├─ TagSelector.tsx          // 标签选择器(前台筛选)
  ├─ TagSelectDialog.tsx      // 标签选择对话框
  ├─ TagCreateForm.tsx        // 标签创建表单
  ├─ TagManagementDialog.tsx  // 标签管理弹窗
  └─ post-config.ts           // 各 PostType 配置中心

components/tiptap/            // TipTap 富文本编辑器
  ├─ TiptapEditor.tsx         // 富文本编辑器主组件
  ├─ TiptapRenderer.tsx       // 前台只读渲染器
  ├─ ImageGridExtension.ts    // 图片网格自定义 Node
  ├─ ImageGridNodeView.tsx    // 编辑态图片网格视图
  ├─ ImageGridReadOnlyView.tsx // 阅读态图片网格视图
  ├─ R2ResourceSelector.tsx   // R2 媒体选择器
  ├─ TableMenu.tsx            // 表格悬浮工具条
  ├─ TableOfContents.tsx      // 目录生成组件
  └─ TranslationButton.tsx    // AI 翻译按钮

lib/cms/
  └─ index.ts                 // CMS 模块工厂函数

actions/posts/                // Server Actions
  ├─ posts.ts                 // 内容 CRUD + 前台读取
  ├─ tags.ts                  // 标签 CRUD
  └─ views.ts                 // 浏览量统计

app/[locale]/(protected)/dashboard/(admin)/blogs/
  ├─ page.tsx                 // 后台列表页
  ├─ Columns.tsx              // 表格列定义
  ├─ new/page.tsx             // 创建页
  └─ [postId]/edit/page.tsx   // 编辑页

app/[locale]/(protected)/dashboard/(admin)/glossary/
  ├─ page.tsx                 // 后台列表页
  ├─ Columns.tsx              // 表格列定义
  ├─ new/page.tsx             // 创建页
  └─ [postId]/edit/page.tsx   // 编辑页

app/[locale]/(basic-layout)/blog/
  ├─ page.tsx                 // 前台列表
  └─ [slug]/
      ├─ page.tsx             // 详情页
      └─ opengraph-image.tsx  // 动态 OG 图片

app/[locale]/(basic-layout)/glossary/
  ├─ page.tsx                 // 前台列表
  └─ [slug]/
      ├─ page.tsx             // 详情页
      └─ opengraph-image.tsx  // 动态 OG 图片

lib/db/schema.ts              // Drizzle 数据模型
  // 相关表:posts、tags、postTags

types/cms.ts                  // TypeScript 类型定义

config/common.ts              // 公共配置(图片路径等)

配置说明

PostConfig 配置项

components/cms/post-config.ts 中,每个内容模块都有以下配置项:

export interface PostConfig {
  postType: PostType;                    // 内容类型
  schema: z.ZodSchema;                   // 验证模式
  actionSchema: z.ZodSchema;             // 操作验证模式
  imagePath: string;                     // 图片存储路径
  enableTags: boolean;                   // 是否启用标签
  localDirectory?: string;               // 本地 MDX 文件目录(可选)
  viewCount: ViewCountConfig;            // 阅读量统计配置
  showCoverInList: boolean;              // 列表页是否显示封面图
  routes: {                              // 路由配置
    list: string;
    create: string;
    edit: (id: string) => string;
  };
}

阅读量统计配置

export interface ViewCountConfig {
  enabled: boolean;      // 是否启用阅读量统计
  mode: 'all' | 'unique'; // 统计模式
  showInUI: boolean;     // 是否在界面显示
}

功能展示

因为 CMS 功能都是所见即所得,所以不再详细介绍,通过图片即可了解功能。

cms
cms
cms
cms
cms
cms

上传图片支持本地上传、外部链接、CloudFlare R2 选择

cms
cms

图片展示支持多张并排

cms

支持插入 YouTube 视频

cms

编辑器内容支持快捷翻译

cms

如何修改内容编辑器功能

内容编辑器的所有功能文件都在 components/tiptap 下,你可以熟悉代码后修改,也可以让 AI 帮你修改。AI 对 Tiptap 非常熟练。

编辑器配置选项

TiptapEditor 组件支持以下配置:

interface TiptapEditorProps {
  content: string;
  onChange: (content: string) => void;
  placeholder?: string;
  disabled?: boolean;
  enableYoutube?: boolean;        // 启用 YouTube 视频
  enableImage?: boolean;          // 启用图片上传
  enableCodeBlock?: boolean;      // 启用代码块
  enableTable?: boolean;          // 启用表格
  enableBlockquote?: boolean;     // 启用引用
  enableHorizontalRule?: boolean; // 启用分割线
  enableR2Selector?: boolean;     // 启用 R2 资源选择器
  r2PublicUrl?: string;           // R2 公共访问 URL
  imageUploadConfig?: {
    maxSize?: number;             // 文件大小限制(字节)
    filenamePrefix?: string;      // 文件名前缀
    path?: string;                // 上传路径
  };
  enableTranslation?: boolean;    // 启用 AI 翻译
  postType?: PostType;            // 内容类型
  outputFormat?: "markdown" | "text" | "html"; // 输出格式
}

如何增加新的内容模块

如果你在开发内容网站,博客和术语表模块可能不够使用,你想增加多个内容模块来丰富网站内容结构,这种需求使用 NEXTY.DEV 模板可以轻松解决。

以新增 guide(指南)模块为例,你只需完成以下步骤:

1. 扩展数据库 Schema

lib/db/schema.ts 中扩展 CMS 内容类型:

export const postTypeEnum = pgEnum('post_type', [
  'blog',
  'glossary',
  'guide', // 新增
])

然后执行数据库迁移命令:

pnpm db:generate
pnpm db:migrate

2. 扩展 CMS 配置

components/cms/post-config.ts 中添加新模块配置:

export const POST_CONFIGS: Record<PostType, PostConfig> = {
  blog: {
    // ... 现有配置
  },
  glossary: {
    // ... 现有配置
  },
  // 新增 guide 配置
  guide: {
    postType: "guide",
    schema: basePostSchema,
    actionSchema: postActionSchema,
    imagePath: GUIDE_IMAGE_PATH, // 需要在 config/common.ts 中定义
    enableTags: true,
    // localDirectory: 'guides', // 如果需要支持本地 MDX 文件
    viewCount: {
      enabled: true,  // 启用阅读量统计
      mode: 'unique', // 使用唯一 IP 统计
      showInUI: true,
    },
    showCoverInList: true,
    routes: {
      list: "/dashboard/guides",
      create: "/dashboard/guides/new",
      edit: (id: string) => `/dashboard/guides/${id}`,
    },
  },
};

3. 添加图片路径配置

config/common.ts 中添加:

export const GUIDE_IMAGE_PATH = "guide-images";
 
export const R2_CATEGORIES: R2Category[] = [
  { name: "All", prefix: "" },
  { name: "Admin Uploads", prefix: `${ADMIN_UPLOAD_IMAGE_PATH}/` },
  { name: "Blogs Images", prefix: `${BLOGS_IMAGE_PATH}/` },
  { name: "Glossary Images", prefix: `${GLOSSARY_IMAGE_PATH}/` },
  { name: "Guide Images", prefix: `${GUIDE_IMAGE_PATH}/` }, // 新增
];

4. 创建后台管理页面

复制 app/[locale]/(protected)/dashboard/(admin)/glossary 文件夹,重命名为 guides

修改以下文件:

guides/page.tsx

// 将 postType 改为 "guide"
const result = await listPostsAction({
  pageIndex: 0,
  pageSize: PAGE_SIZE,
  postType: "guide", // 修改这里
});
 
// 更新配置
<PostDataTable
  config={{
    postType: "guide", // 修改这里
    columns,
    listAction: listPostsAction,
    createUrl: "/dashboard/guides/new", // 修改这里
    enableTags: true,
    searchPlaceholder: "Search guides...", // 修改这里
  }}
  // ...
/>

guides/Columns.tsx:根据需要调整表格列定义。

guides/new/page.tsxguides/[postId]/edit/page.tsx

<PostEditorClient
  postType="guide" // 修改这里
  mode="create" // 或 "edit"
  r2PublicUrl={r2PublicUrl}
  postId={postId}
/>

5. 创建前台展示页面

复制 app/[locale]/(basic-layout)/glossary 文件夹,重命名为 guide

guide/page.tsx

// 创建 CMS 模块实例(在 lib/cms/index.ts 中)
export const guideCms = createCmsModule('guide');
 
// 在页面中使用
const { posts: localPosts } = await guideCms.getLocalList(locale);
 
const initialServerPostsResult = await listPublishedPostsAction({
  pageIndex: 0,
  pageSize: SERVER_POST_PAGE_SIZE,
  postType: "guide", // 修改这里
  locale: locale,
});
 
// 标签
const tagsResult = await listTagsAction({ postType: "guide" });
 
// PostList 组件
<PostList
  postType="guide"
  baseUrl="/guide"
  localPosts={localPosts}
  initialPosts={initialServerPosts}
  initialTotal={totalServerPosts}
  serverTags={serverTags}
  locale={locale}
  pageSize={SERVER_POST_PAGE_SIZE}
  showTagSelector={true}
  showCover={POST_CONFIGS.guide.showCoverInList}
  emptyMessage="No guides found for this tag."
/>

guide/[slug]/page.tsx

// 使用 guideCms 获取内容
const { post, errorCode } = await guideCms.getBySlug(slug, locale);
 
// 阅读量统计
const viewCountConfig = POST_CONFIGS.guide.viewCount;
if (viewCountConfig.enabled) {
  if (viewCountConfig.mode === "unique") {
    await incrementUniqueViewCountAction({ slug, postType: "guide", locale });
  } else {
    await incrementViewCountAction({ slug, postType: "guide", locale });
  }
}
 
// 关联文章
<RelatedPosts
  postId={post.id}
  postType="guide"
  limit={10}
  title="Related Guides"
  locale={locale}
  CardComponent={GuidePostCard}
/>

6. 在 lib/cms/index.ts 中导出新模块

// 在文件末尾添加
export const guideCms = createCmsModule('guide');

7. 更新 sitemap.ts

app/sitemap.ts 中添加 Guide 页面的路由:

// 添加 guide 列表页
{
  url: `${siteUrl}/${locale}/guide`,
  lastModified: new Date(),
  changeFrequency: 'daily',
  priority: 0.8,
}
 
// 添加 guide 详情页
const guideResult = await listPublishedPostsAction({
  locale: locale,
  pageSize: 1000,
  visibility: "public",
  postType: "guide",
});
 
if (guideResult.success && guideResult.data?.posts) {
  guideResult.data.posts.forEach((post) => {
    sitemap.push({
      url: `${siteUrl}/${locale}/guide/${post.slug}`,
      lastModified: post.updatedAt ? new Date(post.updatedAt) : new Date(),
      changeFrequency: 'weekly',
      priority: 0.7,
    });
  });
}

8. 添加国际化翻译

messages/ 目录下的各语言文件中添加相关翻译。

注意事项

  1. 本地 MDX 文件支持:如果新增的 CMS 模块不需要支持本地 MDX 文件,不要设置 localDirectory 配置项,直接使用数据库即可。

  2. 标签系统独立:每个内容模块的标签系统是独立的,通过 postType 字段区分。

  3. 图片路径规划:为每个内容模块设置独立的图片存储路径,便于管理和组织。

  4. 阅读量统计:需要配置 Redis 才能使用阅读量统计功能。如果未配置 Redis,系统会优雅降级,返回 0 计数。

  5. 内容可见性

  • public:所有人可见
  • logged_in:需要登录
  • subscribers:需要订阅(在 actions/posts/posts.tscheckUserSubscription 函数中自定义订阅逻辑)
  1. SEO 优化
  • 未上传封面图时,系统会自动生成动态 OG 图片
  • 记得更新 sitemap.ts 包含新模块的路由
  • 使用 constructMetadata 函数确保正确的元数据

这样一个全新的 CMS 模块就完成了!

如果你觉得自己新增模块太麻烦,请把文档链接发给 AI,AI 会帮你完成所有步骤。

总结

NEXTY.DEV 的 CMS 系统具有以下优势:

  1. 架构清晰:统一的配置中心,易于扩展和维护
  2. 功能完善:从内容创建、编辑到前台展示,一应俱全
  3. 灵活配置:每个模块可独立配置功能特性
  4. 用户体验优秀:TipTap 编辑器功能强大,操作流畅
  5. 开发效率高:新增模块只需少量代码,大部分组件可复用
  6. 类型安全:完整的 TypeScript 类型定义
  7. 性能优化:支持无限滚动、分页、缓存等优化手段

无论是个人博客、企业内容站,还是复杂的多模块内容平台,NEXTY.DEV 的 CMS 系统都能轻松应对!