You fixed the bug three customers complained about. You shipped the feature your top user requested in week one. But nobody noticed -- because the fix lived in a Slack message that scrolled off, and the feature was mentioned once in a Discord announcement that half your users never saw.
That is the silent churn pattern: a customer leaves because they think you never fixed their problem. A changelog page solves this. It is a permanent, searchable record of what changed, and it signals that your product is actively maintained.
Here is how to wire one into a Next.js SaaS Boilerplate -- database, public page, admin form, and optional email digest -- without building it from scratch.
Before writing any code, decide what goes in each entry. Four fields cover everything you need:
feature, improvement, fix, securityKeep entries short. One change, one entry. If you shipped five things at once, write five short entries -- not one long "Spring Release" post that nobody finishes reading.
The category field does two jobs: it lets users filter for what matters to them ("show me only bug fixes"), and it stops you from burying a security patch inside a list of minor tweaks.
With Drizzle ORM, the schema is straightforward:
export const changelogTable = pgTable('changelog', {
id: uuid('id').defaultRandom().primaryKey(),
title: text('title').notNull(),
summary: text('summary').notNull(),
category: text('category', {
enum: ['feature', 'improvement', 'fix', 'security']
}).notNull(),
published_at: timestamp('published_at', { withTimezone: true }).notNull(),
published: boolean('published').notNull().default(false),
author_id: uuid('author_id').references(() => userTable.id).notNull(),
created_at: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
});
The published flag lets you draft entries before they go live. The published_at field is separate from created_at so you can backdate an entry for a hotfix that shipped at 3am but which you write up the next morning.
After adding this to your schema, run npm run db:generate && npm run db:migrate to apply the change. See the module setup guide for the full repository and service layer pattern that keeps your routes thin.
The public page is a server component -- no client-side fetching needed. Your repository layer returns entries ordered by published_at descending, filtered to published = true.
The page structure that works best:
?category=fix, ?category=feature, etc.Grouping by month is more readable than a flat list once you have more than 15 entries. It takes a simple reduce() over the fetched array -- no extra database query.
For the category filter, read searchParams.category in the page and pass it to the repository. Drizzle adds WHERE category = $1 when it is present. The URL stays shareable and the page stays server-renderable with no extra work.
Only admins should create changelog entries. The admin panel pattern applies here: check user.role === 'admin' in the service before allowing any write, and throw HttpError(403) if it fails.
The form fields:
Wire it with React Hook Form and Zod. The schema should validate that published_at is a real date and that category is one of the four enum values. POST to /api/changelog -- the route validates, calls the service, and returns 201.
The highest-value update is one that reaches active users by email, not just users who happen to visit your changelog page that week.
The pattern:
published = true), the service queries for users who opted into product updates.The transactional email guide covers the Resend + react-email setup in detail. The key rule: call the email service from your changelog service, never from the route handler. Keep the email template to one entry per send -- subject line equals the entry title.
A changelog page does three things that are invisible until you measure them:
None of that comes from a Slack announcement that everyone has muted.
If you are starting from the Next.js SaaS Boilerplate, the Drizzle ORM schema pattern, Resend email service, and admin auth layer are already in place. Add the changelogTable, follow the 7-file module convention, create the public server component page, and you have a working changelog before the end of the day.
The next time you ship a fix, your customers will actually see it.