implement
Introduction

Introduction

File-based routing, SSR, and prerendering for implement apps.

@implementjs/kit is the fastest way to build an implement app. You write pages and layouts as files in src/routes, and kit turns them into a fully wired router with typed params, server-side rendering in dev, and a prerendered static site on build — or, with an adapter, a server that renders and answers endpoints per request.

If you have used SvelteKit this will feel very familiar, we borrowed the conventions on purpose.

Under the hood kit is just a Vite plugin. It scans your routes directory, generates the router for you, and serves everything through @implementjs/vite's SSR dev server and prerenderer. There's no runtime of its own, what ships to the browser is the same @implementjs/core router you could have written by hand.

This docs site is built with kit, so everything you read here is running on it right now.

Setup

create-implement-app writes all of this for you — the kit template is its default:

npm create implement-app@latest

That leaves you with a routed app, a layout, an error page, and .implement/ already generated, so it typechecks and runs straight away.

Either way, that's it. Make a src/routes/page.ts that default-exports a component and run vite:

// src/routes/page.ts
import { H1 } from "@implementjs/core";

export default function Page() {
	return H1("Hello, Kit!");
}

Project structure

A kit app looks like this — and is what the kit template generates:

my-app/
├ src/
│  ├ lib/
│  │  ├ components/
│  │  └ utils.ts
│  ├ routes/
│  ├ app.css
│  ├ app.d.ts
│  ├ hooks.server.ts
│  └ index.html
└ static/

The html shell lives at src/index.html. Vite normally only serves an index.html sitting at the project root, so kit serves the one under src/ itself and moves it back to the root of dist/ on build — a root index.html still works if you prefer it there.

src/routes is the routing tree — every file in it is a page, layout, or error boundary, covered in Routing.

src/hooks.server.ts runs on every server request, and src/app.d.ts is where you type what it hands your routes — both covered in Server Hooks. Neither is required.

src/lib is for everything that isn't a route: components, utilities, shared state. Kit aliases @/lib to it automatically — in Vite and, through the generated tsconfig, in TypeScript — so imports stay flat no matter how deep the importing file sits:

import { Button } from "@/lib/components/button";

static/ is for assets served as-is from the site root — static/favicon.png is available at /favicon.png. Vite serves the directory directly in dev and copies it into dist/ on build. It's just Vite's publicDir pointed at static, so set publicDir in your Vite config if you want a different folder.

Global stylesheets like app.css live at the root and get imported from the root layout:

// src/routes/layout.ts
import "../../app.css";

The .implement directory

When kit runs it writes a .implement/ directory next to your Vite config. It contains the client and server entries, the tsconfig you extend, a $types.d.ts for every route directory, and — when you build with an adapter — the staged build the adapter reads from. The whole thing is gitignored (kit writes the .gitignore too) and regenerates itself, so you never edit anything in there.

Because the files are generated by the dev server and build, a fresh clone won't have them yet. That is what kit's CLI is for:

implement-kit sync

It writes .implement/ without running Vite, for tsc --noEmit scripts, CI, and editors. implement-kit is a bin on the package, so it's on your path as soon as kit is installed, and the scaffolded app wires it into three scripts:

{
	"scripts": {
		// runs on install, so a fresh clone is synced before anyone opens it
		"prepare": "implement-kit sync || echo ''",
		"sync": "implement-kit sync",
		"check": "implement-kit sync && tsc --noEmit",
	},
}

There is nothing to configure. The CLI loads your vite.config.ts the way Vite does and takes the options straight off the kit() plugin in it, so the routes and alias you set there are the ones it generates against. It takes --config and --mode when you need to point it at a different config or load one that branches on the mode.

For anything the CLI doesn't cover, the same work is exported as a function:

import { sync } from "@implementjs/kit/sync";

sync("/path/to/app", { alias: { "@/content": "src/content" } });

Options

kit() takes five options:

  • routes — the routes directory relative to your Vite root. Defaults to "src/routes".
  • hooks — the server hooks file relative to your Vite root. Defaults to "src/hooks.server.ts".
  • prerenderfalse to skip prerendering on build, or { entries } to add dynamic routes to it. Covered in SSR & Prerendering.
  • env — where the two environment-variable files live, relative to your Vite root. Defaults to src/lib/env.public.ts and src/lib/env.server.ts, and a file that isn't there turns that half off. Covered in Environment Variables.
  • alias — extra import aliases on top of the automatic @/lib, mapped to paths relative to your Vite root. Like @/lib, each one is wired into both Vite and the generated tsconfig, so the bundler and the typechecker always agree:
kit({
	alias: {
		"@/content": "src/content",
		// a file target aliases a single module
		"@utils": "src/lib/utils.ts",
	},
});

implement-kit sync reads this map out of your Vite config, so a check script never needs a second copy of it.

Where to next