This file is Pitlane's documentation, every page as Markdown: the home page, then every page in the order [llms.txt](https://pitlane.tools/llms.txt) lists it. Each page begins with its title as a level-one heading (`# Title`), then, after a blank line, `Source: ` and the address of the page's own Markdown, then its description as a quote. A page runs until the next level-one heading; none appears anywhere else outside code blocks. # Pitlane Source: https://pitlane.tools/index.md > Pitlane gives you composable packages to help you put your Remix app on the grid. Start with [Pitlane guides](https://pitlane.tools/guides.md). ## Packages ### @pitlane/vite-plugin-remix: Build & develop Development, server, and browser builds, all in one Vite plugin. ```ts title="vite.config.ts" import { remix } from "pitlane/vite-plugin-remix"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [remix()], }); ``` Guide: [Using the Vite plugin](https://pitlane.tools/guides/vite-plugin.md) · Reference: [@pitlane/vite-plugin-remix](https://pitlane.tools/package/vite-plugin-remix/index.md) ### @pitlane/content: Load & validate Your files become typed, queryable collections. ```ts title="app/content.ts" import { createContent } from "pitlane/content"; import * as loaders from "pitlane/content/loaders"; import * as s from "remix/data-schema"; export let content = createContent(c => ({ posts: c.collection({ loader: loaders.glob({ base: "app/posts", pattern: "**/*.md", }), schema: s.object({ title: s.string() }), }), })); ``` Guide: [Content](https://pitlane.tools/guides/content.md) · Reference: [@pitlane/content](https://pitlane.tools/package/content/index.md) ### @pitlane/theme: Style & compose Typed design tokens. Native Remix styling. ```ts title="app/theme.ts" import { createTheme } from "pitlane/theme"; import * as s from "pitlane/theme/schema"; export let { token: t, Theme } = createTheme({ schema: { color: s.color(), spacing: s.scale(), }, tokens: { color: { brand: "#eb2027" }, spacing: "0.25rem", }, }); ``` Guide: [Theme](https://pitlane.tools/guides/theme.md) · Reference: [@pitlane/theme](https://pitlane.tools/package/theme/index.md) ### @pitlane/crawler: Crawl & prerender Follow your app’s links without starting an HTTP server. ```ts title="scripts/crawl.ts" import { crawl } from "pitlane/crawler"; import router from "../app/entry.server.ts"; for await (let { pathname, response } of crawl(router)) { console.log(pathname, response.status); } ``` Guide: [Crawler](https://pitlane.tools/guides/crawler.md) · Reference: [@pitlane/crawler](https://pitlane.tools/package/crawler/index.md) ### @pitlane/data-table-d1: Connect your data Remix’s database API, backed by your Cloudflare D1 binding. ```ts title="app/database.ts" import { createD1Database } from "pitlane/data-table-d1"; import { env } from "cloudflare:workers"; export let db = createD1Database(env.DB); ``` Guide: [Cloudflare D1](https://pitlane.tools/guides/cloudflare-d1.md) · Reference: [@pitlane/data-table-d1](https://pitlane.tools/package/data-table-d1/index.md) ## Deployment - [Cloudflare](https://pitlane.tools/deploy/cloudflare.md): workerd - [Netlify](https://pitlane.tools/deploy/netlify.md): Node.js - [Vercel](https://pitlane.tools/deploy/vercel.md): Node.js - [Railway](https://pitlane.tools/deploy/railway.md): Node.js, Bun, or Deno - [Deno Deploy](https://pitlane.tools/deploy/deno-deploy.md): Deno - [GitHub Pages](https://pitlane.tools/deploy/github-pages.md): Browser ## Start from a template ```sh vpx giget github:pitlane-tools/templates/cloudflare my-app ``` # Pitlane guides Source: https://pitlane.tools/guides.md > An introduction to Pitlane, a composable toolkit for Remix 3, with guides for each task. Pitlane is a composable toolkit for [Remix 3](https://remix.run). Its Vite plugin runs the dev server and builds the app for its host. Additional packages resolve built assets and handle content and styling, with a Cloudflare D1 driver and an in-memory crawler available separately. Remix owns routing and UI. Pitlane's packages compose with its APIs: content collections validate with `remix/data-schema`, and the D1 driver plugs into `remix/data-table`. These guides explain how each piece behaves and when to reach for it. The [API reference](https://pitlane.tools/package/vite-plugin-remix/index.md) documents every export separately. ## Guides ### Build and develop - [Using the Vite plugin](https://pitlane.tools/guides/vite-plugin.md) What `remix()` does in dev, preview, and the build, including the `clientEntry()` transform. - [Hot module replacement](https://pitlane.tools/guides/hmr.md) Component state across edits and server-data revalidation through the frame runtime. - [Single-page apps](https://pitlane.tools/guides/spa.md) A client-rendered app with `remix({ server: false })`, and what the plugin still does without a server. - [Prerendering](https://pitlane.tools/guides/prerendering.md) Rendering paths to static HTML during `vite build`, and where those paths come from. - [Fetch server](https://pitlane.tools/guides/fetch-server.md) Serving a `fetch` handler through Vite's dev server with `fetchServer()`. ### Assets - [Asset resolution](https://pitlane.tools/guides/assets.md) Script entries, stylesheets, and preloads from `createAssetResolver()`, with or without Remix. - [Manifest integrations](https://pitlane.tools/guides/asset-build.md) Turning another bundler's output into an asset manifest with `createAssetManifest()`. ### Deploy - [Cloudflare Workers](https://pitlane.tools/deploy/cloudflare.md) Local development with workerd and deployment through Wrangler. - [Netlify](https://pitlane.tools/deploy/netlify.md) A one-file Netlify Function around the built fetch handler. - [Vercel](https://pitlane.tools/deploy/vercel.md) Deploying server functions through Nitro. - [Railway](https://pitlane.tools/deploy/railway.md) Container deployment with a choice of runtime. - [Deno Deploy](https://pitlane.tools/deploy/deno-deploy.md) The fetch handler behind a short `Deno.serve` entrypoint. - [GitHub Pages](https://pitlane.tools/deploy/github-pages.md) A single-page app or a fully prerendered site, with the base path a project site needs. ### Content and styling - [Content](https://pitlane.tools/guides/content.md) Querying schema-validated collections of Markdown, MDX, JSON, or YAML. - [Theme](https://pitlane.tools/guides/theme.md) Typed design tokens for styling with `css()` mixins. ### Crawling - [Crawler](https://pitlane.tools/guides/crawler.md) Walking the app's router in memory for sitemaps and link checks. ### Data - [Cloudflare D1](https://pitlane.tools/guides/cloudflare-d1.md) Running `remix/data-table` on a D1 binding, and the D1 limits the driver cannot hide. Content, prerendering, and the Railway and Deno Deploy guides also cover apps that run straight from source with no build step. Their pages switch between the Vite and No Build versions. ## Start a new app The [starter templates](https://github.com/pitlane-tools/templates) are one guest book app, wired once for each deploy target. Scaffold the Cloudflare one with [giget](https://github.com/unjs/giget): ```sh npx giget github:pitlane-tools/templates/cloudflare my-app ``` The repository names the other targets, and the [deploy guides](#deploy) cover each one. ## Add Pitlane to a Remix app Install the `pitlane` package. It needs `remix` 3 and `vite` 8.1 or later as peer dependencies. ```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:`. Then add the Vite plugin from `pitlane/vite-plugin-remix` to `vite.config.ts`: ```ts title="vite.config.ts" import { remix } from "pitlane/vite-plugin-remix"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [remix()], }); ``` [Using the Vite plugin](https://pitlane.tools/guides/vite-plugin.md#the-three-file-core) shows the server and browser entry files the plugin expects, and what changes between dev, preview, and the build. ### Imports `pitlane` installs every Pitlane package and re-exports each one under a subpath named after it: `@pitlane/vite-plugin-remix` becomes `pitlane/vite-plugin-remix`, and `@pitlane/theme/schema` becomes `pitlane/theme/schema`. Bare `pitlane` has no exports, so every import names a subpath. Each package also installs on its own under its `@pitlane/*` name. That route suits an app that uses one package or needs a release newer than the one `pitlane` pins. [Installing Pitlane](https://pitlane.tools/guides/umbrella.md) compares the installs and explains the version pins, with a full subpath reference. ## For coding agents `pitlane` releases after `1.0.0-alpha.1` install these guides with the package, so an agent working in an app can read the version the app depends on. [Documentation for coding agents](https://pitlane.tools/guides/umbrella.md#documentation-for-coding-agents) explains where the files are and how to point an agent at them. # Installing Pitlane Source: https://pitlane.tools/guides/umbrella.md > Install every Pitlane package with one dependency, import each through a pitlane/* subpath, and decide when the scoped @pitlane/* packages fit better. `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:`. `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/` 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:`. 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/` to `pitlane/`, 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/.md` | Each guide, such as `guides/vite-plugin.md` | | `guides/deploy/.md` | Each deploy guide, such as `guides/deploy/cloudflare.md` | | `dist//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. # Using the Vite plugin Source: https://pitlane.tools/guides/vite-plugin.md > How the remix() plugin from pitlane/vite-plugin-remix behaves in your app day to day, across document assets, the clientEntry() transform and its footguns, dev-server semantics, preview, and the build. This guide covers living with [`remix()`](https://pitlane.tools/package/vite-plugin-remix/function/remix.md) from `pitlane/vite-plugin-remix` day to day. It explains how your document and islands find their browser assets and what the `clientEntry()` transform does, including what it deliberately refuses to do. Dev, preview, and the production build each get their own section. Hot module replacement has its own guide, [Hot module replacement](https://pitlane.tools/guides/hmr.md). Exact API signatures live in the generated [API reference](https://pitlane.tools/package/vite-plugin-remix/index.md). Per-target deployment configs live in the [deploy guides](#deploying). ## Installation ```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:`. The `pitlane` package goes in `dependencies` because app code imports runtime subpaths such as `pitlane/assets`. It requires `remix@^3.0.0` and `vite@>=8.1` as peer dependencies. The underlying `@pitlane/vite-plugin-remix` package also installs on its own, with the same peers. An app using it directly adds `@pitlane/assets` to `dependencies` for the resolver. The [Pitlane package guide](https://pitlane.tools/guides/umbrella.md) compares the two. The tested matrix is **Vite 8.1 and later** (Rolldown) and **Vite+ 1.0 and later** (`vp`) with `remix@3.0.0`. Vite 7 and 8.0 are not supported, and neither are the Vite+ 0.x releases built on them. ### Upgrading from `pitlane/dev` The plugin was published as `@pitlane/dev` and `pitlane/dev` until this release. The rename comes with a new way to name assets, and there are no aliases for the old paths. Change these together: | Before | After | | --- | --- | | `remix` from `pitlane/dev` | `remix` from `pitlane/vite-plugin-remix` | | `revalidate` from `pitlane/dev/hmr` | `revalidate` from `pitlane/vite-plugin-remix/hmr` | | `import clientAssets from "./entry.browser.ts?assets=client"`, then `.entry` | `(await assets.getScriptEntry("app/entry.browser.ts")).href` | | `clientAssets.js` as `modulepreload` links | `.preloads` from the same call | | `import serverAssets from "./entry.server.tsx?assets=ssr"`, then `.css` | `await assets.getStylesheets("app/entry.server.tsx")` | | `mergeAssets(…)` from `pitlane/dev/runtime` | plain arrays; the resolver deduplicates | | `render()` without options, or a hand-written `resolveClientEntry` | `render({ assets })` from `remix/middleware/render` | | `"types": ["pitlane/dev/assets"]` in `tsconfig.json` | delete it | | `import { HMR } from "pitlane:dev"` and `` in the document | delete both; add the `server:update` listener from the [HMR guide](https://pitlane.tools/guides/hmr.md#setup) to the browser entry | [Document assets](#document-assets) shows the resulting files. ### Upgrading from a Remix prerelease Upgrade Remix and the Pitlane packages together. The stable-compatible releases require `remix@^3.0.0`. Their previous prerelease-compatible versions remain available for apps that have not migrated. - Replace `remix/ui` imports with `remix/component`, including `/server`, `/jsx-runtime`, and `/jsx-dev-runtime` subpaths. Set TypeScript's `jsxImportSource` and Satteri's `mdx.jsxImportSource` to `remix/component`. - Replace `remix/ui-hmr` with `remix/component-hmr`. For no-build apps, rename the asset loader from `uiHmr()` to `componentHmr()` and the Node hook to `remix/component-hmr/node`. The Vite plugin supplies its own HMR integration. - Raw HTML props such as `innerHTML` now require `unsafeHTML()` from `remix/component`. It marks trusted or already-sanitized content. It does not sanitize strings. Pitlane's content renderer and `` wrap their own markup this way, and neither sanitizes it. - Animation utilities and headless primitives are separate `@remix-run/ui` imports. The old styled components and style mixins, including `button()` and `inputStyle`, were removed. Replacing those requires an application change. See the [Remix 3.0.0 release notes](https://github.com/remix-run/remix/releases/tag/remix%403.0.0) for the upstream entrypoint changes. ## Templates Prefer starting from a working app? The [`pitlane-tools/templates`](https://github.com/pitlane-tools/templates) monorepo has the same Remix 3 guest book wired for eight deploy targets. Cloudflare uses D1. Netlify uses Netlify DB, while Vercel pairs Nitro with Postgres. Railway runs on Node, Bun, or Deno. The remaining templates cover Deno Deploy and GitHub Pages, which runs in the browser with IndexedDB. Scaffold one with [giget](https://github.com/unjs/giget): ```sh npx giget github:pitlane-tools/templates/