如何使用 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 功能都是所见即所得,所以不再详细介绍,通过图片即可了解功能。






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


图片展示支持多张并排

支持插入 YouTube 视频

编辑器内容支持快捷翻译

如何修改内容编辑器功能
内容编辑器的所有功能文件都在 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:migrate2. 扩展 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.tsx 和 guides/[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/ 目录下的各语言文件中添加相关翻译。
注意事项
-
本地 MDX 文件支持:如果新增的 CMS 模块不需要支持本地 MDX 文件,不要设置
localDirectory配置项,直接使用数据库即可。 -
标签系统独立:每个内容模块的标签系统是独立的,通过
postType字段区分。 -
图片路径规划:为每个内容模块设置独立的图片存储路径,便于管理和组织。
-
阅读量统计:需要配置 Redis 才能使用阅读量统计功能。如果未配置 Redis,系统会优雅降级,返回 0 计数。
-
内容可见性:
public:所有人可见logged_in:需要登录subscribers:需要订阅(在actions/posts/posts.ts的checkUserSubscription函数中自定义订阅逻辑)
- SEO 优化:
- 未上传封面图时,系统会自动生成动态 OG 图片
- 记得更新
sitemap.ts包含新模块的路由 - 使用
constructMetadata函数确保正确的元数据
这样一个全新的 CMS 模块就完成了!
如果你觉得自己新增模块太麻烦,请把文档链接发给 AI,AI 会帮你完成所有步骤。
总结
NEXTY.DEV 的 CMS 系统具有以下优势:
- 架构清晰:统一的配置中心,易于扩展和维护
- 功能完善:从内容创建、编辑到前台展示,一应俱全
- 灵活配置:每个模块可独立配置功能特性
- 用户体验优秀:TipTap 编辑器功能强大,操作流畅
- 开发效率高:新增模块只需少量代码,大部分组件可复用
- 类型安全:完整的 TypeScript 类型定义
- 性能优化:支持无限滚动、分页、缓存等优化手段
无论是个人博客、企业内容站,还是复杂的多模块内容平台,NEXTY.DEV 的 CMS 系统都能轻松应对!