---
url: /docs/development.md
---
# Developer Info {#development}
This page briefly summarizes the development workflow for the `tanstack-head-controller` monorepo.
## Prerequisites
* Node.js 22+
* pnpm 10.x
## Setup
::: code-group
```sh [ni]
ni
```
```sh [npm]
npm install
```
```sh [pnpm]
pnpm install
```
:::
## Build
Run the following from the repository root.
::: code-group
```sh [ni]
nr build
```
```sh [npm]
npm build
```
```sh [pnpm]
pnpm build
```
:::
Internally, `scripts/build.ts` does the following for each package.
* Generates JavaScript with SWC
* Generates declaration files (`d.ts`) with TypeScript
To build individual packages:
::: code-group
```sh [node]
node ./scripts/build.ts tanstack-head-controller
node ./scripts/build.ts thc-plugin-merge
```
:::
## Main packages
* `packages/tanstack-head-controller`: core library
* `packages/thc-plugin-merge`: head merge plugin
* `packages/thc-plugin-ttplate`: title template plugin
* `examples/tanstack-react`: integration example
## Docs update checklist
* If you update Japanese pages, keep English pages in the same structure.
* Keep code block language/file labels aligned (for example, `tsx [router.tsx]`).
* Verify that all referenced links resolve correctly.
When needed, also see [Quick Start](./getting-started) and [Usage](./usage).
---
---
url: /docs/getting-started.md
---
# Quick Start {#getting-started}
This page shows the shortest path to integrate TanStack Head Controller into a TanStack Router app.
## 1. Install packages
::: code-group
```sh [ni]
ni tanstack-head-controller thc-plugin-merge thc-plugin-ttplate
```
```sh [npm]
npm install tanstack-head-controller thc-plugin-merge thc-plugin-ttplate
```
```sh [pnpm]
pnpm add tanstack-head-controller thc-plugin-merge thc-plugin-ttplate
```
:::
## 2. Create a controller and place it in router context
```tsx [router.tsx]
import { createRouter } from '@tanstack/react-router'
import { createHeadController } from 'tanstack-head-controller'
import { thcMerge } from 'thc-plugin-merge'
import { thcTitleTemplate } from 'thc-plugin-ttplate'
export const router = createRouter({
routeTree,
context: {
...createHeadController({
plugins: [
thcMerge(),
thcTitleTemplate({
siteName: "My App"
})
]
})
},
})
```
## 3. Render in your root document head
```tsx [__root.tsx]
import { HeadControllerRender } from 'tanstack-head-controller'
function RootDocument({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
## 4. Define head at route level
```tsx [about.tsx]
export const Route = createFileRoute('/about')({
head: () => ({
meta: [
{ title: 'About' },
{ property: 'og:title', content: 'About' },
],
}),
})
```
## 5. Optionally edit route context
This example uses `editContext`, but the key idea is simple: keep existing route context intact and add only the settings you need.
```tsx [about.tsx]
import { editContext } from 'tanstack-head-controller/context'
export const Route = createFileRoute('/about')({
context: (ctx) =>
editContext(ctx, {
configs: { debug: true },
}),
})
```
Next, see [Usage](./usage) for plugin patterns and practical tips.
---
---
url: /contributors.md
---
---
---
url: /docs/usage.md
---
# Usage {#usage}
This page covers practical patterns for using TanStack Head Controller in real apps.
## Baseline integration
1. Place the controller in router context (for example, `headCtrlr`)
2. Return head from routes
3. Place the render component (`HeadControllerRender`) in the root document head
## Combine plugins
```tsx
import { createHeadController } from 'tanstack-head-controller'
import { thcMerge } from 'thc-plugin-merge'
import { thcTitleTemplate } from 'thc-plugin-ttplate'
export const thc = createHeadController({
plugins: [
thcMerge(),
thcTitleTemplate({
siteName: 'TanStack Starter',
separator: ' | ',
}),
],
})
```
## Edit route context
Use the helper (`editContext`) to merge controller-specific values while preserving existing route context.
```tsx [about.tsx]
import { editContext } from 'tanstack-head-controller/context'
export const Route = createFileRoute('/about')({
context: (ctx) =>
editContext(ctx, {
configs: {
debug: true,
},
}),
})
```
## Write a custom plugin
```tsx
import { createThcPlugin } from 'tanstack-head-controller/plugins'
export const forceNoIndex = () =>
createThcPlugin({
name: 'app.force-noindex',
transform(head) {
return {
...head,
meta: [...(head.meta ?? []), { name: 'robots', content: 'noindex' }],
}
},
})
```
## Ordering guidance
* Plugins run in registration order
* Put normalization plugins first and final formatting plugins later
## Common production patterns
* Define base metadata at layout level and override it at page level
* Standardize OG tags with one plugin
* Centralize environment-based robots behavior with a plugin
Continue with [Quick Start](./getting-started) and [Developer Info](./development).
---
---
url: /docs/what-is-thc.md
---
# What is TanStack Head Controller? {#what-is-thc}
TanStack Head Controller is a small library that collects head data from TanStack Router route matches, transforms that data through plugins, and renders the final tags in your document head.
::: tip
If you are starting from scratch, go to the [Quick Start](./getting-started).
:::
## What it does
* Collects route-level page data (meta, links, styles, and scripts)
* Applies plugin transforms in sequence
* Renders the resolved head with a React component
## Core API
* Create a controller with configs and plugins (`createHeadController`)
* Resolve and render head tags for the current route (`HeadControllerRender`)
* Safely merge controller-related values into route context (`editContext`)
## Plugin model
Plugins implement a transform function:
* Input: current head payload and controller context
* Output: transformed head payload
Built-in examples in this monorepo:
* Merge duplicate-like metadata entries (`thc-plugin-merge`)
## Typical use cases
* Keep head logic centralized across nested layouts and page routes
* Standardize title patterns without repeating route-level logic
* Add custom SEO transforms without modifying the core library
## Notes
* Current implementation resolves head primarily from route matches
* Script entries are collected from route script definitions (`headScripts`)
Continue with [Quick Start](./getting-started) and [Usage](./usage).