---
title: "Installing Pitlane"
description: "Install every Pitlane package with one dependency, import each through a pitlane/* subpath, and decide when the scoped @pitlane/* packages fit better."
url: "https://pitlane.tools/guides/umbrella"
---

# Installing Pitlane

`pitlane` is one install for all seven Pitlane packages. Each package is published on its own under `@pitlane/*`, and `pitlane` re-exports every one of them through focused subpaths, the way `remix` re-exports the `@remix-run/*` packages:

```ts
import { remix } from "pitlane/vite-plugin-remix";
import { createAssetResolver } from "pitlane/assets";
import { createTheme } from "pitlane/theme";
```

A subpath has the same exports as the package it re-exports, so every API page under [`@pitlane/vite-plugin-remix`](https://pitlane.tools/package/vite-plugin-remix/index.md), [`@pitlane/assets`](https://pitlane.tools/package/assets/index.md), and the rest applies to the matching `pitlane/*` import unchanged. The scoped packages stay independently usable. An app that wants only one of them can install it alone.

## Install

```sh
npm add pitlane
```

With yarn, pnpm, bun, vp, vlt, or nub, run the same command with the manager's name in place of `npm`. With deno, put its name in place of `npm` and write each package as `npm:<package>`.

`pitlane` declares three peer dependencies, lifted from the packages it installs:

| Peer | Range | Required |
| --- | --- | --- |
| `remix` | `^3.0.0` | Yes |
| `vite` | `>=8.1.0` | Yes |
| `satteri` | `^0.10.5` | Only when a content collection holds `.md` or `.mdx` files |

Install `pitlane` as a dependency, not a dev dependency, whenever the app imports a runtime subpath such as `pitlane/assets`, `pitlane/theme`, `pitlane/content`, or `pitlane/data-table-d1`. Those imports run in production, so a production install has to include the package that provides them.

`pitlane` has no command-line interface. To start a new app, copy one of the [starter templates](https://github.com/pitlane-tools/templates).

## Subpaths

Bare `pitlane` has no exports, so `import … from "pitlane"` fails. Every import names a subpath, and each subpath mirrors a scoped package export:

| Import | Re-exports | Use it for |
| --- | --- | --- |
| `pitlane/vite-plugin-remix` | [`@pitlane/vite-plugin-remix`](https://pitlane.tools/package/vite-plugin-remix/index.md) | The `remix()` Vite plugin |
| `pitlane/vite-plugin-remix/hmr` | [`@pitlane/vite-plugin-remix/hmr`](https://pitlane.tools/package/vite-plugin-remix/hmr.md) | `revalidate()`, the browser entry's server-update helper |
| `pitlane/assets` | [`@pitlane/assets`](https://pitlane.tools/package/assets/index.md) | `createAssetResolver()` and `renderImportMap()` |
| `pitlane/assets/manifest` | [`@pitlane/assets/manifest`](https://pitlane.tools/package/assets/manifest.md) | The asset manifest the build supplies, passed to `createAssetResolver()` |
| `pitlane/assets/build` | [`@pitlane/assets/build`](https://pitlane.tools/package/assets/build.md) | `createAssetManifest()`, for bundler integration authors |
| `pitlane/assets/vite-plugin` | [`@pitlane/assets/vite-plugin`](https://pitlane.tools/package/assets/vite-plugin.md) | The `assets()` Vite plugin, for apps without `remix()` |
| `pitlane/vite-plugin-fetch-server` | [`@pitlane/vite-plugin-fetch-server`](https://pitlane.tools/package/vite-plugin-fetch-server/index.md) | The `fetchServer()` development request bridge |
| `pitlane/theme` | [`@pitlane/theme`](https://pitlane.tools/package/theme/index.md) | `createTheme()`, `css()`, and `tva()` |
| `pitlane/theme/schema` | [`@pitlane/theme/schema`](https://pitlane.tools/package/theme/schema.md) | Schema factories |
| `pitlane/theme/default` | [`@pitlane/theme/default`](https://pitlane.tools/package/theme/default.md) | Tailwind CSS v4's primitives as `DefaultTheme` |
| `pitlane/theme/dtcg` | [`@pitlane/theme/dtcg`](https://pitlane.tools/package/theme/dtcg.md) | Conversion to and from Design Tokens Community Group documents |
| `pitlane/content` | [`@pitlane/content`](https://pitlane.tools/package/content/index.md) | `createContent()` and collections |
| `pitlane/content/loaders` | [`@pitlane/content/loaders`](https://pitlane.tools/package/content/loaders.md) | The `glob()` and `file()` loaders |
| `pitlane/content/vite-plugin` | [`@pitlane/content/vite-plugin`](https://pitlane.tools/package/content/vite-plugin.md) | The `contentLayer()` Vite plugin |
| `pitlane/content/hot` | [`@pitlane/content/hot`](https://pitlane.tools/package/content/hot.md) | Browser reloads for content without a build |
| `pitlane/content/satteri` | [`@pitlane/content/satteri`](https://pitlane.tools/package/content/satteri.md) | The `headings()` and `rawStyles()` Satteri plugins |
| `pitlane/crawler` | [`@pitlane/crawler`](https://pitlane.tools/package/crawler/index.md) | `crawl()` and `staticPaths()` |
| `pitlane/data-table-d1` | [`@pitlane/data-table-d1`](https://pitlane.tools/package/data-table-d1/index.md) | The Cloudflare D1 driver for `remix/data-table` |
| `pitlane/data-table-d1/migrations` | [`@pitlane/data-table-d1/migrations`](https://pitlane.tools/package/data-table-d1/migrations.md) | `generateD1Migrations()`, build tooling kept out of the Worker |

A subpath is the scoped package name without its scope, followed by the package's own subpath: `@pitlane/theme/schema` becomes `pitlane/theme/schema`.

## Versions

Each `pitlane` release depends on an exact version of every scoped package, and its changelog lists those versions. Installing `pitlane@1.0.0-alpha.1` installs the package versions that release was built and documented against, never newer ones.

The umbrella is released by hand when enough package changes have accumulated. A scoped package's newest release can be ahead of the version the current `pitlane` pins. Need a fix before the next umbrella release? Install that scoped package directly and use its `@pitlane/<name>` import. [Mixing the two](#mixing-the-two) explains how the installs coexist.

`1.0.0-alpha.1` is the first umbrella release. It pins `@pitlane/dev@0.7.1`, `@pitlane/theme@0.5.0`, `@pitlane/content@0.3.1`, `@pitlane/crawler@0.3.0`, and `@pitlane/data-table-d1@0.3.0`. Releases before it, `0.0.x`, were a placeholder with no usable exports.

Alpha releases are published to npm's `latest` tag until a stable `1.0.0` exists, so a plain install picks up the newest alpha. Pin an exact version, such as `pitlane@1.0.0-alpha.1`, to decide when the app moves to the next one.

## Production installs

`pitlane` installs every package, `@pitlane/vite-plugin-remix` and its Vite peer included. Because an app with runtime imports lists it in `dependencies`, a production install that omits dev dependencies still installs the Vite plugins and Vite itself. `remix` makes the same trade for its CLI and test runner. The cost is install size and time on the production host.

## Scoped packages

The scoped packages are the lean alternative. Put `@pitlane/vite-plugin-remix` in dev dependencies and only the runtime packages the app imports in dependencies. For an app that resolves its assets and uses the theme and content collections:

```sh
npm add @pitlane/assets @pitlane/theme @pitlane/content
npm add -D @pitlane/vite-plugin-remix
```

With yarn, pnpm, bun, vp, vlt, or nub, run the same command with the manager's name in place of `npm`. With deno, put its name in place of `npm` and write each package as `npm:<package>`.

Then import from the scoped names, such as `@pitlane/vite-plugin-remix` and `@pitlane/theme/schema`. Choose this when production install size matters, when an app uses a single package, or when it needs a package release newer than the umbrella pins.

## Migrate from scoped packages

1. Install `pitlane` as shown in [Install](#install).
2. Rewrite the public imports listed in [Subpaths](#subpaths) from `@pitlane/<name>` to `pitlane/<name>`, keeping the listed subpath: `@pitlane/content/vite-plugin` becomes `pitlane/content/vite-plugin`, and `@pitlane/assets/manifest` becomes `pitlane/assets/manifest`.
3. Remove a scoped package from `package.json` only when nothing in the app or its tooling imports it directly.

The umbrella excludes `@pitlane/content/internal/*`. Custom bundler integrations using those unstable APIs must keep their scoped imports and a direct `@pitlane/content` dependency. Keep that dependency at the umbrella's pinned content version when both are used, so the plugin and collections share the same content package.

### Mixing the two

An app can keep a direct `@pitlane/*` dependency next to `pitlane`. Different versions mean two copies of that package. The `pitlane/*` import reaches the pinned copy, while the `@pitlane/*` import reaches the direct dependency. Use one import path consistently for each package.

## Documentation for coding agents

`pitlane` releases after `1.0.0-alpha.1` install these guides and every package's README with the package. A coding agent working in the app reads documentation for the version the app depends on, offline, instead of what it remembers or what pitlane.tools last deployed. The layout follows the one `remix` uses for its installed documentation:

| Path in `node_modules/pitlane/` | What it holds |
| --- | --- |
| `INDEX.md` | Every guide listed by task and every export listed by subpath, each linking the installed file that covers it |
| `guides/index.md` | The [guides overview](https://pitlane.tools/guides.md) |
| `guides/<slug>.md` | Each guide, such as `guides/vite-plugin.md` |
| `guides/deploy/<slug>.md` | Each deploy guide, such as `guides/deploy/cloudflare.md` |
| `dist/<package>/README.md` | Each scoped package's README beside its compiled code, such as `dist/vite-plugin-remix/README.md` for `pitlane/vite-plugin-remix` and its subpaths |

These are the same Markdown pages pitlane.tools publishes. A link between two of them leads to the installed copy, so following one never leaves the installed version. Links to the [API reference](https://pitlane.tools/package/vite-plugin-remix/index.md) stay on pitlane.tools. The installed type declarations carry the same TSDoc, so an editor or agent can read an export's signature and documentation from the `.d.ts` file instead.

An agent has to know the index exists. Point it there with a line in the app's `AGENTS.md`:

```md
Before changing Pitlane code, read `node_modules/pitlane/INDEX.md`; prefer the installed documentation over memory or the latest website.
```

An app that installs only scoped `@pitlane/*` packages has none of these files. It can read the same pages as Markdown through [`llms.txt`](https://pitlane.tools/llms.txt), which describes whatever was last deployed.
