Skip to content

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

  1. .template-base, at your app's root, records the kit commit your app is at. The update writes it; the kit never ships it.
  2. pnpm template:update fetches the template remote and takes everything the kit changed between that commit and the target: the kit's main, or a tag you name.
  3. It applies that change to your branch with git cherry-pick, as one commit together with the new .template-base. The commit is titled chore(template): update to <tag or commit>, and its body lists the subject of every kit commit it brings.
  4. 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.

CommandWhat it does
pnpm template:updateTakes the kit's main.
pnpm template:update v1.4.0Takes that tag. A branch name or a commit from the kit works too.
pnpm template:update --no-commitStages 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/, except app/types/; server/api/, except the kit's own routes; and public/.
  • server/jobs/ and server/listeners/, except the files the kit ships there.
  • Every custom/ directory and custom.* file. The kit ships at most an empty one and never writes it again: provider adapters in server/*/adapters/custom/ and server/signals/*/custom/, your tables in server/database/custom/, their migrations in server/database/migrations/custom/, your events in server/events/custom.d.ts.
  • content/ and i18n/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:

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-commit is declined for good. The staged .template-base already names the target, so the next update starts past it. To take it later, pass the commit .template-base named before that update as --base. git log -p -- .template-base shows 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.