Changelog
The boilerplate ships with a single-page changelog at /changelog that lets you publish release notes without setting up a CMS, database table, or extra build step. Entries live in a typed TypeScript file, so adding a release is a one-file commit.
Where things live
| Concern | File |
|---|---|
| Release data (the only file you edit per release) | app/data/changelog.ts |
| Rendering (layout, year grouping, styling) | app/pages/changelog/index.vue |
| Header nav entry | app/components/header/MainHeader.vue |
| Page chrome copy (title, description, type labels) | i18n/locales/*.json (changelog.* keys) |
Data shape
app/data/changelog.ts exports two things: the types and the entries.
export type ChangeType = 'feature' | 'improvement' | 'fix'
export interface Change {
type: ChangeType
text: string
}
export interface ChangelogEntry {
version: string // e.g. "1.2.0" — used as the anchor id
date: string // ISO yyyy-mm-dd
changes: Change[]
}
export const CHANGELOG_ENTRIES: ChangelogEntry[] = [
// newest first
]
Rules:
- Entries are ordered newest first. The page does not re-sort them.
versionis a plain string. Semver (1.2.0) is recommended but not enforced.datemust be ISOyyyy-mm-dd. The page renders a relative date ("2 weeks ago") with the absolute date in a hover tooltip.- Each
versionbecomes a deep-linkable anchor like/changelog#v1-2-0.
Change type taxonomy
Pick one of three types per change. The page renders each with an emoji and a translated type label:
feature— new capability that didn't exist before. New page, new integration, new API. Rendered with ✨.improvement— meaningful change to existing behavior. Refactors users will notice, performance wins, UX polish, expanded docs. Rendered with ⚡.fix— bug fixes and regressions. Rendered with 🐛.
If you find yourself wanting more types (e.g. security, breaking, deprecated), extend ChangeType in app/data/changelog.ts and map the new value to an emoji in app/pages/changelog/index.vue (changeEmoji).
Adding a release
- Open
app/data/changelog.ts. - Copy the top entry and paste it above itself.
- Bump
versionand setdateto today. - Replace
changeswith the new entries.
That's the whole workflow. No migrations, no CMS, no rebuild story — TypeScript validates the shape at compile time.
Why entry text isn't translated
The boilerplate's i18n covers the page chrome (title, description, type labels) but not the change text itself. Release notes are usually authored once per release in a single language by whoever shipped the change. Forcing them through $t() would mean opening four locale files per release, and untranslated entries would silently fall back to the key string.
If your project genuinely needs translated release notes, opt in at the call site:
{ type: 'feature', text: 'changelog.entries.v1_2_0.faster_search' }
Then resolve it in the template:
<span class="text-sm leading-6">{{ $t(change.text) }}</span>
You'll need to add the key to every locale file. For most projects this is more friction than benefit; default to plain strings.
Reusing the data elsewhere
CHANGELOG_ENTRIES is a plain export, so anything in the app can import it. Some ideas:
- A "What's new" card on the landing page that surfaces the latest entry.
- A
/api/changelogendpoint that returns the array as JSON for external consumers. - An RSS feed at
/changelog.xmlbuilt in a Nitro route from the same array.
The data file has no Nuxt or Vue imports, so it can be consumed from server routes, plugins, or other components without complication.
Removing the changelog
If your project doesn't need a public changelog, remove these five things:
- Delete
app/data/changelog.ts. - Delete
app/pages/changelog/index.vue. - Delete this docs file (
content/docs/4.customization/5.changelog.md). - Remove the
changelogblock andheader.navigation.changelogkey from eachi18n/locales/*.jsonfile. - Remove the
/changelogentry fromnavigationItemsinapp/components/header/MainHeader.vueandapp/components/header/LandingHeader.vue(and the unusedHistoryicon imports from both files).
No database migrations or build config changes are needed.