How to Use the CMS Module
Good to know
In the v3.2.3 - 3.2.4 updates, NEXTY.DEV's CMS was refactored using TipTap, gaining more features and a more perfect content editor experience.
Supported Features
The CMS functionality provided by the NEXTY.DEV boilerplate is among the most comprehensive of all SaaS boilerplates, with very rich features. Through TipTap's rich plugin ecosystem, you can easily extend even more functionality.
The following are the features already supported by the NEXTY.DEV boilerplate:
Core Features
- Multi-content Module Support: Default provides two modules: blog and glossary
- Unified Architecture Design: Common base components and shared data tables, allowing you to create new content modules by adding minimal files and code
- Multi-content Source Support: Supports local MDX file rendering and server-side CMS data rendering, with each module independently configurable
- Reading Statistics: Supports reading statistics with two modes:
allmode: Counts every page loaduniquemode: Counts only once per hour for the same IP address
- Table of Contents (TOC): Detail pages automatically generate article directories, supporting desktop sidebar and mobile collapsible display
- Related Articles Recommendation: Detail pages support displaying related articles (based on the same tag)
- Cover Image Support: Supports local upload, external links, and CloudFlare R2 selection
- AI Translation: Supports built-in translation button in the editor, enabling quick translation of selected content
- Flexible Configuration: Most toolbar plugins support enabling or disabling through configuration
Content Management Features
Creating and editing content supports:
- Basic Information:
- Title, URL alias (slug), description
- Tag management, supports up to 5 tags
- Cover image upload (supports local upload, external links, CloudFlare R2 selection)
- Multi-language Support:
- Select content language (supports all configured languages)
- Supports different languages with the same slug design
- Advanced Features:
- Status Settings: Draft, published, archived
- Visibility Control: Public, logged_in, subscribers
- Pinning Feature: Supports pinning important content for display
- Content Protection: For content requiring login or subscription, the system automatically displays access restriction prompts
Dashboard Management Features
- Data Table Display:
- Paginated browsing of all content
- Supports search filtering (title, slug, description)
- Supports filtering by status and language
- Tag Management:
- Create, edit, delete tags
- Tags are associated with content modules (each module has an independent tag system)
- Batch Operations:
- Duplicate content functionality
- Delete content
- Cover Image Management:
- Supports configuring whether to display cover images on list pages (
showCoverInListconfiguration item) - Automatically generates dynamic OG images when no cover image is uploaded
- Supports configuring whether to display cover images on list pages (
Directory Structure
components/cms/ // CMS common components
├─ PostDataTable.tsx // Dashboard list & pagination
├─ PostEditorClient.tsx // Create/edit entry (client component)
├─ PostForm.tsx // Form component
├─ PostList.tsx // Frontend infinite scroll list
├─ PostCard.tsx // Frontend content card
├─ PostListActions.tsx // Dashboard action buttons (edit, duplicate, delete)
├─ RelatedPosts.tsx // Related articles component
├─ ContentRestrictionMessage.tsx // Content access restriction prompt
├─ ImageUpload.tsx // Image upload component (supports R2)
├─ TagInput.tsx // Tag input component
├─ TagSelector.tsx // Tag selector (frontend filtering)
├─ TagSelectDialog.tsx // Tag selection dialog
├─ TagCreateForm.tsx // Tag creation form
├─ TagManagementDialog.tsx // Tag management dialog
└─ post-config.ts // PostType configuration center
components/tiptap/ // TipTap rich text editor
├─ TiptapEditor.tsx // Rich text editor main component
├─ TiptapRenderer.tsx // Frontend read-only renderer
├─ ImageGridExtension.ts // Image grid custom Node
├─ ImageGridNodeView.tsx // Edit mode image grid view
├─ ImageGridReadOnlyView.tsx // Read mode image grid view
├─ R2ResourceSelector.tsx // R2 media selector
├─ TableMenu.tsx // Table floating toolbar
├─ TableOfContents.tsx // Table of contents generation component
└─ TranslationButton.tsx // AI translation button
lib/cms/
└─ index.ts // CMS module factory function
actions/posts/ // Server Actions
├─ posts.ts // Content CRUD + frontend reading
├─ tags.ts // Tag CRUD
└─ views.ts // View count statistics
app/[locale]/(protected)/dashboard/(admin)/blogs/
├─ page.tsx // Dashboard list page
├─ Columns.tsx // Table column definitions
├─ new/page.tsx // Create page
└─ [postId]/edit/page.tsx // Edit page
app/[locale]/(protected)/dashboard/(admin)/glossary/
├─ page.tsx // Dashboard list page
├─ Columns.tsx // Table column definitions
├─ new/page.tsx // Create page
└─ [postId]/edit/page.tsx // Edit page
app/[locale]/(basic-layout)/blog/
├─ page.tsx // Frontend list
└─ [slug]/
├─ page.tsx // Detail page
└─ opengraph-image.tsx // Dynamic OG image
app/[locale]/(basic-layout)/glossary/
├─ page.tsx // Frontend list
└─ [slug]/
├─ page.tsx // Detail page
└─ opengraph-image.tsx // Dynamic OG image
lib/db/schema.ts // Drizzle data model
// Related tables: posts, tags, postTags
types/cms.ts // TypeScript type definitions
config/common.ts // Common configuration (image paths, etc.)
Configuration
PostConfig Configuration Items
In components/cms/post-config.ts, each content module has the following configuration items:
export interface PostConfig {
postType: PostType; // Content type
schema: z.ZodSchema; // Validation schema
actionSchema: z.ZodSchema; // Action validation schema
imagePath: string; // Image storage path
enableTags: boolean; // Whether to enable tags
localDirectory?: string; // Local MDX file directory (optional)
viewCount: ViewCountConfig; // View count statistics configuration
showCoverInList: boolean; // Whether to display cover image on list page
routes: { // Route configuration
list: string;
create: string;
edit: (id: string) => string;
};
}View Count Statistics Configuration
export interface ViewCountConfig {
enabled: boolean; // Whether to enable view count statistics
mode: 'all' | 'unique'; // Statistics mode
showInUI: boolean; // Whether to display in UI
}Feature Showcase
Since CMS features are all WYSIWYG, we won't go into detail here. You can understand the features through the images.






Image upload supports local upload, external links, and CloudFlare R2 selection


Image display supports multiple images side by side

Supports inserting YouTube videos

Editor content supports quick translation

How to Modify Content Editor Features
All feature files for the content editor are under components/tiptap. You can familiarize yourself with the code and modify it, or let AI help you modify it. AI is very proficient with Tiptap.
Editor Configuration Options
The TiptapEditor component supports the following configuration:
interface TiptapEditorProps {
content: string;
onChange: (content: string) => void;
placeholder?: string;
disabled?: boolean;
enableYoutube?: boolean; // Enable YouTube video
enableImage?: boolean; // Enable image upload
enableCodeBlock?: boolean; // Enable code block
enableTable?: boolean; // Enable table
enableBlockquote?: boolean; // Enable blockquote
enableHorizontalRule?: boolean; // Enable horizontal rule
enableR2Selector?: boolean; // Enable R2 resource selector
r2PublicUrl?: string; // R2 public access URL
imageUploadConfig?: {
maxSize?: number; // File size limit (bytes)
filenamePrefix?: string; // Filename prefix
path?: string; // Upload path
};
enableTranslation?: boolean; // Enable AI translation
postType?: PostType; // Content type
outputFormat?: "markdown" | "text" | "html"; // Output format
}How to Add New Content Modules
If you're developing a content website, the blog and glossary modules might not be enough. You want to add multiple content modules to enrich your website's content structure. This requirement can be easily solved using the NEXTY.DEV boilerplate.
Taking adding a new guide module as an example, you only need to complete the following steps:
1. Extend Database Schema
Extend the CMS content type in lib/db/schema.ts:
export const postTypeEnum = pgEnum('post_type', [
'blog',
'glossary',
'guide', // New addition
])Then execute the database migration commands:
pnpm db:generate
pnpm db:migrate2. Extend CMS Configuration
Add the new module configuration in components/cms/post-config.ts:
export const POST_CONFIGS: Record<PostType, PostConfig> = {
blog: {
// ... existing configuration
},
glossary: {
// ... existing configuration
},
// New guide configuration
guide: {
postType: "guide",
schema: basePostSchema,
actionSchema: postActionSchema,
imagePath: GUIDE_IMAGE_PATH, // Needs to be defined in config/common.ts
enableTags: true,
// localDirectory: 'guides', // If you need to support local MDX files
viewCount: {
enabled: true, // Enable view count statistics
mode: 'unique', // Use unique IP statistics
showInUI: true,
},
showCoverInList: true,
routes: {
list: "/dashboard/guides",
create: "/dashboard/guides/new",
edit: (id: string) => `/dashboard/guides/${id}`,
},
},
};3. Add Image Path Configuration
Add in 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}/` }, // New addition
];4. Create Dashboard Management Pages
Copy the app/[locale]/(protected)/dashboard/(admin)/glossary folder and rename it to guides.
Modify the following files:
guides/page.tsx:
// Change postType to "guide"
const result = await listPostsAction({
pageIndex: 0,
pageSize: PAGE_SIZE,
postType: "guide", // Modify here
});
// Update configuration
<PostDataTable
config={{
postType: "guide", // Modify here
columns,
listAction: listPostsAction,
createUrl: "/dashboard/guides/new", // Modify here
enableTags: true,
searchPlaceholder: "Search guides...", // Modify here
}}
// ...
/>guides/Columns.tsx: Adjust table column definitions as needed.
guides/new/page.tsx and guides/[postId]/edit/page.tsx:
<PostEditorClient
postType="guide" // Modify here
mode="create" // or "edit"
r2PublicUrl={r2PublicUrl}
postId={postId}
/>5. Create Frontend Display Pages
Copy the app/[locale]/(basic-layout)/glossary folder and rename it to guide.
guide/page.tsx:
// Create CMS module instance (in lib/cms/index.ts)
export const guideCms = createCmsModule('guide');
// Use in page
const { posts: localPosts } = await guideCms.getLocalList(locale);
const initialServerPostsResult = await listPublishedPostsAction({
pageIndex: 0,
pageSize: SERVER_POST_PAGE_SIZE,
postType: "guide", // Modify here
locale: locale,
});
// Tags
const tagsResult = await listTagsAction({ postType: "guide" });
// PostList component
<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:
// Use guideCms to get content
const { post, errorCode } = await guideCms.getBySlug(slug, locale);
// View count statistics
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 });
}
}
// Related articles
<RelatedPosts
postId={post.id}
postType="guide"
limit={10}
title="Related Guides"
locale={locale}
CardComponent={GuidePostCard}
/>6. Export New Module in lib/cms/index.ts
// Add at the end of the file
export const guideCms = createCmsModule('guide');7. Update sitemap.ts
Add Guide page routes in app/sitemap.ts:
// Add guide list page
{
url: `${siteUrl}/${locale}/guide`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.8,
}
// Add guide detail pages
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. Add Internationalization Translations
Add relevant translations in the language files under the messages/ directory.
Notes
-
Local MDX File Support: If the new CMS module doesn't need to support local MDX files, don't set the
localDirectoryconfiguration item, just use the database directly. -
Independent Tag System: Each content module's tag system is independent, distinguished by the
postTypefield. -
Image Path Planning: Set independent image storage paths for each content module for easier management and organization.
-
View Count Statistics: Redis needs to be configured to use the view count statistics feature. If Redis is not configured, the system will gracefully degrade and return 0 counts.
-
Content Visibility:
public: Visible to everyonelogged_in: Requires loginsubscribers: Requires subscription (customize subscription logic in thecheckUserSubscriptionfunction inactions/posts/posts.ts)
- SEO Optimization:
- When no cover image is uploaded, the system automatically generates dynamic OG images
- Remember to update
sitemap.tsto include routes for the new module - Use the
constructMetadatafunction to ensure correct metadata
That's it! A brand new CMS module is complete!
If you find adding modules yourself too troublesome, send the documentation link to AI, and AI will help you complete all the steps.
Summary
NEXTY.DEV's CMS system has the following advantages:
- Clear Architecture: Unified configuration center, easy to extend and maintain
- Comprehensive Features: Everything from content creation and editing to frontend display
- Flexible Configuration: Each module can independently configure feature characteristics
- Excellent User Experience: TipTap editor is powerful and smooth to operate
- High Development Efficiency: Adding new modules requires minimal code, most components are reusable
- Type Safety: Complete TypeScript type definitions
- Performance Optimization: Supports infinite scroll, pagination, caching, and other optimization methods
Whether it's a personal blog, enterprise content site, or complex multi-module content platform, NEXTY.DEV's CMS system can handle it with ease!