> ## Documentation Index
> Fetch the complete documentation index at: https://better-router.bansal.io/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript

> tsconfig, loader props, and how to type env bindings.

better-router is ESM. Use `"module": "ESNext"` and `"moduleResolution": "bundler"` so import attributes (islands) and `.ts` extensions in generated code typecheck.

## `tsconfig.json`

```json title="tsconfig.json" theme={null}
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "noEmit": true,
    "isolatedModules": true,
    "skipLibCheck": true,
    "types": ["vite/client"]
  },
  "include": ["src", ".better-router", "vite.config.ts"]
}
```

```ts title="src/vite-env.d.ts" theme={null}
/// <reference types="vite/client" />
/// <reference types="better-router/client" />
```

`.better-router` is generated on `vite build` (route manifest). Include it if you want that file in the project; it is gitignored.

## Loader and page types

There is no generated route type yet. Share a props type between the loader and the page:

```ts title="src/routes/projects/[id].server.tsx" theme={null}
import type { LoaderArgs } from "better-router";

export type ProjectPageProps = {
  id: string;
  name: string;
};

export async function loader({ params }: LoaderArgs): Promise<ProjectPageProps> {
  return { id: params.id!, name: "Acme" };
}
```

```tsx title="src/routes/projects/[id].tsx" theme={null}
import { useLoaderData, useParams } from "better-router/react";
import type { ProjectPageProps } from "./[id].server";

export default function ProjectPage({ name }: ProjectPageProps) {
  const data = useLoaderData<ProjectPageProps>();
  const { id } = useParams<{ id: string }>();
  return (
    <h1>
      {data.name ?? name} ({id})
    </h1>
  );
}
```

On the first SSR pass, props come from the loader. After SPA navigation, `useLoaderData()` is the source of truth. Reading both (`data.name ?? name`) covers both.

`.server.tsx` is server-only. Importing a **type** from it into a page is fine. Importing a value (a function, a database client) is not — that pulls server code into the client bundle.

## Hono context and env

```ts theme={null}
import { getEnv, getHonoContext } from "better-router";

export async function loader() {
  const env = getEnv();
  const c = getHonoContext();
  return { host: c.req.header("host"), url: env.APP_URL };
}
```

Loaders also receive `c` directly as `LoaderArgs.c`.

Augment bindings and variables when you add Wrangler keys or middleware state:

```ts title="src/env.d.ts" theme={null}
declare module "better-router" {
  interface AppBindings {
    DB: D1Database;
    KV: KVNamespace;
    BUCKET: R2Bucket;
    APP_URL: string;
  }
  interface AppVariables {
    requestId: string;
  }
}
```

Then `getEnv().DB` and `c.get("requestId")` typecheck. See [Bindings](/bindings).

## Client vs server imports

| Import from                   | Safe in `.tsx` pages | Safe in `.server.tsx` / `.ts` APIs |
| ----------------------------- | -------------------- | ---------------------------------- |
| `better-router/react`         | yes                  | no need                            |
| `getEnv()` / Hono / Node APIs | no                   | yes                                |
| type-only from `.server.tsx`  | yes                  | yes                                |
