Get started
Updating from the kit
Your app keeps the kit as a second git remote named template and takes each
kit update as one commit on a branch you review. Nothing is installed or
vendored, and your main shares none of the kit's history. You need this page
when you create the app and each time you take an update.
How it works
.template-base, at your app's root, records the kit commit your app is at. The update writes it; the kit never ships it.pnpm template:updatefetches thetemplateremote and takes everything the kit changed between that commit and the target: the kit'smain, or a tag you name.- It applies that change to your branch with
git cherry-pick, as one commit together with the new.template-base. The commit is titledchore(template): update to <tag or commit>, and its body lists the subject of every kit commit it brings. - Where you and the kit changed the same lines, it stops with conflict markers, like any cherry-pick.
The first update has no .template-base to read. It looks for the kit commit
whose files are identical to your app's first commit, so that first commit has
to be the unchanged kit.
Create your app
Click "Use this template" on the kit repository, nuxter-app/kit, clone your
new repository and add the remote once:
git remote add template https://github.com/nuxter-app/kit.git
To host the repository anywhere else, clone the kit and drop its history:
git clone https://github.com/nuxter-app/kit.git my-app
cd my-app
git update-ref -d HEAD
git commit -m "chore: start from the template"
git branch --unset-upstream
git remote rename origin template
git remote add origin <your repository>
git update-ref -d HEAD removes the kit's history and leaves its files staged
exactly as the kit has them, so your first commit is the kit byte for byte.
To start the app in a folder of a repository that already exists, such as a monorepo, see Apps in a subfolder.
Whichever way you start, make your own changes in the commits after the first.
Take an update
git switch -c template-update-$(date +%Y%m%d)
pnpm template:update
The working tree must be clean; the script refuses while tracked files have
uncommitted changes. When it finishes, push the branch and open a pull
request. Squash-merge it to keep main linear.
| Command | What it does |
|---|---|
pnpm template:update | Takes the kit's main. |
pnpm template:update v1.4.0 | Takes that tag. A branch name or a commit from the kit works too. |
pnpm template:update --no-commit | Stages the update without committing, so you can read the whole diff first. |
pnpm template:update --base <tag or commit> | Treats that kit commit as the one your app is at, in place of .template-base. |
Use --base when the script can't work out where your app started: when your
first commit wasn't an unchanged copy of the kit, or when .template-base
names a commit the remote doesn't have. Both errors say so.
If your app is already at the target, the script prints
Already at the template's <commit>. and changes nothing.
Once .template-base exists, this shows what the next update brings:
git fetch template
git log "$(cat .template-base)"..template/main
Resolve conflicts
A conflicted update lists the paths and exits with status 1:
Template 3f2a9c1d0b7e..8e41d5a2c6f0 applied with conflicts in:
nuxt.config.ts
i18n/locales/en.json
Resolve each one: `git add` keeps your resolution, `git rm` keeps a file deleted.
Then `git cherry-pick --continue`. To back out entirely: `git cherry-pick --abort`.
Fix the markers in each file and git add it. A file you deleted that the kit
changed shows as a modify/delete conflict; git rm <path> keeps it deleted.
Then finish:
git cherry-pick --continue
git cherry-pick --abort puts everything back as it was. After --no-commit
there is no cherry-pick to continue: resolve, then git commit, or back out
with git reset --merge.
If an update is too much to resolve at once, abort it and take one release at a time by passing each tag in turn.
Dependency conflicts
Both sides change package.json, and the kit's lockfile moves with every
dependency refresh. Keep both sides' entries in package.json, then rebuild
the lockfile instead of merging it by hand:
git checkout --theirs pnpm-lock.yaml
pnpm install
git add package.json pnpm-lock.yaml
During the update, --theirs is the kit's side. pnpm install resolves the
merged package.json against the kit's lockfile and adds your packages back.
What conflicts, and what does not
Conflicts come from app code in files the kit owns. Keep your code in files that are yours and an update applies cleanly.
Yours
The kit never writes these, or only adds to them:
app/, exceptapp/types/;server/api/, except the kit's own routes; andpublic/.server/jobs/andserver/listeners/, except the files the kit ships there.- Every
custom/directory andcustom.*file. The kit ships at most an empty one and never writes it again: provider adapters inserver/*/adapters/custom/andserver/signals/*/custom/, your tables inserver/database/custom/, their migrations inserver/database/migrations/custom/, your events inserver/events/custom.d.ts. content/andi18n/locales/. The kit still adds a key for each new string of its own, and now and then rewords one of its strings. A value you changed conflicts when the kit rewords that same key..template-base.
Shared
These conflict on purpose. starter.config.ts gains a key when the kit adds an
option, and you choose its value. package.json and pnpm-lock.yaml carry
both sides' dependencies. scripts/blog/blog.config.mjs holds your blog
pipeline settings and gains the kit's new options and models.
The kit's
Everything else. That includes the subsystems under server/, server/utils/,
server/types/, shared/, modules/, build/, emails/, test/,
scripts/, nuxt.config.ts, wrangler.jsonc, the drizzle*.ts configs,
.github/, README.md, AGENTS.md, .env.example and the dotfiles at the
root. You can edit any of them, and each edit conflicts the next time the kit
changes the same lines.
Nitro registers every file in server/middleware/, server/plugins/,
server/routes/ and server/tasks/, so your own middleware or plugin is a new
file there, under a name the kit doesn't use. A test of your own is a new file
in test/.
Two habits keep updates small:
- Change behaviour through
starter.config.tsbefore you edit a kit file. - Delete a kit file you don't want rather than emptying it. A deleted file
conflicts only when the kit changes it, and
git rmresolves that. An emptied one conflicts on every change. The example paywall,server/api/example-paid.get.ts(Licensees only) andapp/components/PaidFeatureCard.vue(Licensees only), is there to be deleted.
Migrations
Your tables go in server/database/custom/schema.ts, and pnpm db:generate
writes their migrations to server/database/migrations/custom/. The kit's
schema and migrations sit beside them in files you never edit, so an update
brings new kit migrations without a conflict, and pnpm db:migrate applies
them. Extend a kit table with a table of your own keyed by its id, never with a
column in server/database/schema.ts. A kit migration that renames or drops a
column comes in a major release, and the changelog names it. The two migration
chains are covered on Database.
Apps in a subfolder
Several apps can each start from the kit in a folder of their own:
git remote add template https://github.com/nuxter-app/kit.git
git fetch template
git read-tree --prefix=apps/web/ -u template/main
git commit -m "feat(web): start from the template"
git read-tree --prefix stages the kit's files under the folder unchanged, so
the commit that adds the folder is the unmodified kit. The first update matches
against the folder as that commit left it.
Run the update from the app's folder. It writes apps/web/.template-base,
changes nothing outside the folder, and titles the commit
chore(template): update apps/web to <tag or commit>, moving part of that into
the body when the title would run long. In a pnpm workspace, run it from the
workspace root:
pnpm --filter ./apps/web template:update
Or run node scripts/template/update.mjs inside the folder. The kit ships its
own pnpm-workspace.yaml, so pnpm template:update run inside the folder
treats it as a separate workspace and reinstalls against the folder's
pnpm-lock.yaml first. The update then stops on the files that install
changed.
GitHub runs workflows only from the repository root, so the app's .github/
comes along and never runs. Copy the workflows you want to the root. Commit
hooks install only at the root too: in a folder, pnpm install reports
.git can't be found and skips them.
Releases
With no argument the update takes the kit's main, which is always
deployable. Tags mark releases:
pnpm template:update v1.4.0
You can move from tags to main at any time. Moving from main to tags waits
for the next tag, because a tag older than the commit in .template-base is
refused: taking it would revert kit changes you already have.
The version number says how much the update asks of you:
- Major: you need to act. A renamed variable or config key, a moved or removed kit name, a migration to read before you deploy, or a change likely to conflict with your files.
- Minor: something new that applies without your help.
- Patch: fixes only.
CHANGELOG.md lists every release, and each entry opens
with what to do before you take it.
Good to know
- The remote must be named
template. The script looks it up by that name and stops if it is missing. - A file you unstage after
--no-commitis declined for good. The staged.template-basealready names the target, so the next update starts past it. To take it later, pass the commit.template-basenamed before that update as--base.git log -p -- .template-baseshows it, and the files you already took apply as no-ops. - A folder renamed only by letter case confuses the script. Give the folder on disk the name the last commit has, then run it again.
- Keep the kit's
LICENSE, under any name, in every repository that holds the kit. Your own license can sit beside it.
A link with a lock opens the kit’s private repository, which every licensee can read.