Claude Code Boilerplate
FeaturesPricingBlogDocs
Get started →

Product

  • Features
  • Pricing
  • Skills

Compare

  • vs ShipFast
  • vs MakerKit
  • vs supastarter

Resources

  • Docs
  • Blog
  • Discord

Legal

  • License
  • Privacy Policy
  • Terms of Service
Claude Code Boilerplate

© 2026 Claude Code Boilerplate. All rights reserved.

← All posts

How to Build a SaaS Changelog Page With Next.js -- Release Notes That Reduce Churn

July 17, 2026
nextjssaasdrizzle-ormresendboilerplate

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.

What a Good Changelog Entry Looks Like

Before writing any code, decide what goes in each entry. Four fields cover everything you need:

  • title -- one sentence describing what changed ("File uploads now support drag and drop")
  • summary -- 2-4 sentences explaining what it means for the user, not for the developer
  • category -- one of: feature, improvement, fix, security
  • published_at -- when it went live, not when you wrote the entry

Keep 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.

The Database Schema

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 Changelog Page

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:

  1. A header with a one-line description ("What we shipped and when")
  2. A category filter -- links to ?category=fix, ?category=feature, etc.
  3. A list of entries grouped by month

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.

The Admin Posting Form

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:

  • title (text input)
  • category (select with the four allowed values)
  • summary (textarea, 2-4 sentences)
  • published_at (date + time input)
  • published toggle (checkbox)

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.

Notify Subscribers When You Publish

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:

  1. When an entry is published (on creation or when you flip published = true), the service queries for users who opted into product updates.
  2. Send a short email via Resend with the entry title, category badge, summary, and a link to the full changelog.

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.

What This Gets You

A changelog page does three things that are invisible until you measure them:

  • Support deflection -- users who search your changelog for "drag and drop" find the answer without opening a support ticket.
  • Trust signals -- a changelog with 30 entries over six months tells a prospect that you ship consistently and fix reported bugs.
  • Reactivation -- a user who cancelled three months ago may return after seeing the fix for their specific complaint in your monthly digest email.

None of that comes from a Slack announcement that everyone has muted.

Build It Today

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.