toiljs 0.0.115 → 0.0.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +16 -7
  3. package/TYPESCRIPT_LAW.md +1946 -2073
  4. package/build/backend/.tsbuildinfo +1 -1
  5. package/build/cli/.tsbuildinfo +1 -1
  6. package/build/cli/index.js +2375 -258
  7. package/build/client/.tsbuildinfo +1 -1
  8. package/build/client/dev/devtools.js +1 -1
  9. package/build/client/dev/error-overlay.js +1 -1
  10. package/build/client/routing/mount.js +1 -1
  11. package/build/compiler/.tsbuildinfo +1 -1
  12. package/build/compiler/auth-emails.js +7 -3
  13. package/build/compiler/emails.js +1 -1
  14. package/build/compiler/index.d.ts +1 -0
  15. package/build/compiler/index.js +25 -4
  16. package/build/compiler/pages.js +2 -3
  17. package/build/compiler/prerender.d.ts +3 -8
  18. package/build/compiler/prerender.js +88 -127
  19. package/build/compiler/ssg.js +3 -4
  20. package/build/compiler/template-build.js +3 -4
  21. package/build/compiler/toil-docs.generated.js +6 -5
  22. package/build/compiler/vite.js +3 -6
  23. package/build/devserver/.tsbuildinfo +1 -1
  24. package/build/devserver/db/database.js +7 -3
  25. package/build/devserver/db/types.js +1 -1
  26. package/build/devserver/production.js +4 -3
  27. package/build/devserver/runtime/crypto.d.ts +1 -1
  28. package/build/io/.tsbuildinfo +1 -1
  29. package/build/logger/.tsbuildinfo +1 -1
  30. package/build/shared/.tsbuildinfo +1 -1
  31. package/examples/basic/client/routes/analytics.tsx +7 -5
  32. package/examples/basic/server/tsconfig.json +1 -3
  33. package/package.json +45 -44
  34. package/presets/no-uint8array-tostring.js +120 -153
  35. package/presets/oxlint.d.ts +5 -0
  36. package/presets/oxlint.js +224 -0
  37. package/server/runtime/tsconfig.json +1 -3
  38. package/tsconfig.json +2 -11
  39. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  40. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
  41. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  42. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  43. package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
  44. package/.github/changelog-config.json +0 -45
  45. package/.github/dependabot.yml +0 -27
  46. package/.github/workflows/ci.yml +0 -222
  47. package/.vscode/settings.json +0 -10
  48. package/docs/README.md +0 -107
  49. package/docs/auth/README.md +0 -104
  50. package/docs/auth/configuration.md +0 -105
  51. package/docs/auth/emails.md +0 -92
  52. package/docs/auth/extending.md +0 -202
  53. package/docs/auth/how-it-works.md +0 -138
  54. package/docs/auth/usage.md +0 -188
  55. package/docs/backend/README.md +0 -143
  56. package/docs/backend/data.md +0 -351
  57. package/docs/backend/rest.md +0 -402
  58. package/docs/backend/rpc.md +0 -226
  59. package/docs/background/README.md +0 -114
  60. package/docs/background/daemons.md +0 -276
  61. package/docs/background/derive.md +0 -179
  62. package/docs/cli/README.md +0 -379
  63. package/docs/concepts/ai-guide.md +0 -132
  64. package/docs/concepts/config.md +0 -416
  65. package/docs/concepts/decorators.md +0 -127
  66. package/docs/concepts/security.md +0 -108
  67. package/docs/concepts/tiers.md +0 -166
  68. package/docs/concepts/types.md +0 -216
  69. package/docs/database/README.md +0 -143
  70. package/docs/database/capacity.md +0 -350
  71. package/docs/database/counters.md +0 -174
  72. package/docs/database/documents.md +0 -277
  73. package/docs/database/events.md +0 -307
  74. package/docs/database/membership.md +0 -246
  75. package/docs/database/setup.md +0 -155
  76. package/docs/database/unique.md +0 -216
  77. package/docs/database/views.md +0 -246
  78. package/docs/frontend/README.md +0 -104
  79. package/docs/frontend/components.md +0 -286
  80. package/docs/frontend/data-fetching.md +0 -260
  81. package/docs/frontend/images.md +0 -148
  82. package/docs/frontend/metadata.md +0 -344
  83. package/docs/frontend/navigation.md +0 -296
  84. package/docs/frontend/rendering.md +0 -236
  85. package/docs/frontend/routing.md +0 -346
  86. package/docs/frontend/scripts.md +0 -118
  87. package/docs/frontend/search.md +0 -191
  88. package/docs/frontend/styling.md +0 -147
  89. package/docs/frontend/toil-global.md +0 -216
  90. package/docs/getting-started/README.md +0 -81
  91. package/docs/getting-started/create-project.md +0 -131
  92. package/docs/getting-started/deploy.md +0 -101
  93. package/docs/getting-started/first-app.md +0 -215
  94. package/docs/getting-started/installation.md +0 -124
  95. package/docs/getting-started/migrating.md +0 -125
  96. package/docs/getting-started/project-structure.md +0 -163
  97. package/docs/introduction/README.md +0 -66
  98. package/docs/introduction/design-principles.md +0 -81
  99. package/docs/introduction/distributed.md +0 -91
  100. package/docs/introduction/how-it-works.md +0 -121
  101. package/docs/introduction/hyperscale.md +0 -70
  102. package/docs/introduction/modern-stack.md +0 -85
  103. package/docs/introduction/vs-other-frameworks.md +0 -64
  104. package/docs/introduction/why-toil.md +0 -99
  105. package/docs/llms.txt +0 -95
  106. package/docs/realtime/README.md +0 -102
  107. package/docs/realtime/channels.md +0 -211
  108. package/docs/realtime/streams.md +0 -369
  109. package/docs/services/README.md +0 -60
  110. package/docs/services/analytics.md +0 -279
  111. package/docs/services/caching.md +0 -175
  112. package/docs/services/cookies.md +0 -235
  113. package/docs/services/crypto.md +0 -209
  114. package/docs/services/email.md +0 -289
  115. package/docs/services/environment.md +0 -141
  116. package/docs/services/ratelimit.md +0 -174
  117. package/docs/services/time.md +0 -85
  118. package/eslint.config.js +0 -57
  119. package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
  120. package/presets/eslint.js +0 -88
  121. package/src/backend/index.ts +0 -204
  122. package/src/cli/configure.ts +0 -363
  123. package/src/cli/create.ts +0 -924
  124. package/src/cli/db.ts +0 -158
  125. package/src/cli/diagnostics.ts +0 -665
  126. package/src/cli/doctor.ts +0 -982
  127. package/src/cli/features.ts +0 -170
  128. package/src/cli/index.ts +0 -326
  129. package/src/cli/notify.ts +0 -120
  130. package/src/cli/proc.ts +0 -56
  131. package/src/cli/ui.ts +0 -169
  132. package/src/cli/update.ts +0 -248
  133. package/src/cli/updates.ts +0 -92
  134. package/src/cli/validate.ts +0 -31
  135. package/src/cli/version-check.ts +0 -125
  136. package/src/client/auth.ts +0 -869
  137. package/src/client/channel/channel.ts +0 -146
  138. package/src/client/components/Form.tsx +0 -65
  139. package/src/client/components/Image.tsx +0 -160
  140. package/src/client/components/Script.tsx +0 -113
  141. package/src/client/components/Slot.tsx +0 -21
  142. package/src/client/dev/devtools.tsx +0 -1089
  143. package/src/client/dev/error-overlay.tsx +0 -225
  144. package/src/client/errors.ts +0 -11
  145. package/src/client/head/head.ts +0 -182
  146. package/src/client/head/metadata.ts +0 -110
  147. package/src/client/index.ts +0 -130
  148. package/src/client/navigation/Link.tsx +0 -108
  149. package/src/client/navigation/NavLink.tsx +0 -86
  150. package/src/client/navigation/navigation.ts +0 -235
  151. package/src/client/navigation/prefetch.ts +0 -169
  152. package/src/client/navigation/scroll.ts +0 -53
  153. package/src/client/routing/Router.tsx +0 -216
  154. package/src/client/routing/action.ts +0 -122
  155. package/src/client/routing/error-boundary.tsx +0 -43
  156. package/src/client/routing/hooks.ts +0 -140
  157. package/src/client/routing/lazy.ts +0 -77
  158. package/src/client/routing/loader.ts +0 -381
  159. package/src/client/routing/match.ts +0 -47
  160. package/src/client/routing/mount.tsx +0 -90
  161. package/src/client/routing/params-context.ts +0 -10
  162. package/src/client/routing/slot-context.ts +0 -7
  163. package/src/client/rpc.ts +0 -97
  164. package/src/client/search/search.ts +0 -189
  165. package/src/client/search/use-page-search.ts +0 -73
  166. package/src/client/ssr/markers.tsx +0 -172
  167. package/src/client/stream/client.ts +0 -237
  168. package/src/client/types.ts +0 -73
  169. package/src/compiler/auth-emails.ts +0 -312
  170. package/src/compiler/config.ts +0 -369
  171. package/src/compiler/docs.ts +0 -127
  172. package/src/compiler/email-preview.ts +0 -314
  173. package/src/compiler/emails.ts +0 -404
  174. package/src/compiler/fonts.ts +0 -89
  175. package/src/compiler/generate.ts +0 -471
  176. package/src/compiler/image-blur.ts +0 -42
  177. package/src/compiler/image-report.ts +0 -88
  178. package/src/compiler/index.ts +0 -1362
  179. package/src/compiler/pages.ts +0 -49
  180. package/src/compiler/plugin.ts +0 -391
  181. package/src/compiler/prerender.ts +0 -263
  182. package/src/compiler/routes.ts +0 -170
  183. package/src/compiler/seo.ts +0 -415
  184. package/src/compiler/sri.ts +0 -144
  185. package/src/compiler/ssg.ts +0 -177
  186. package/src/compiler/ssr-codegen.ts +0 -85
  187. package/src/compiler/template-build.ts +0 -676
  188. package/src/compiler/template.ts +0 -262
  189. package/src/compiler/toil-docs.generated.ts +0 -77
  190. package/src/compiler/vite.ts +0 -245
  191. package/src/devserver/analytics/index.ts +0 -317
  192. package/src/devserver/config/dotenv.ts +0 -102
  193. package/src/devserver/config/env.ts +0 -30
  194. package/src/devserver/config/ratelimit.ts +0 -123
  195. package/src/devserver/daemon/catalog.ts +0 -120
  196. package/src/devserver/daemon/cron.ts +0 -87
  197. package/src/devserver/daemon/host.ts +0 -134
  198. package/src/devserver/daemon/index.ts +0 -369
  199. package/src/devserver/daemon/runtime.ts +0 -48
  200. package/src/devserver/db/catalog.ts +0 -109
  201. package/src/devserver/db/database.ts +0 -1834
  202. package/src/devserver/db/derives.ts +0 -121
  203. package/src/devserver/db/index.ts +0 -20
  204. package/src/devserver/db/routeKinds.ts +0 -191
  205. package/src/devserver/db/types.ts +0 -168
  206. package/src/devserver/email/caps.ts +0 -0
  207. package/src/devserver/email/config.ts +0 -141
  208. package/src/devserver/email/console.ts +0 -348
  209. package/src/devserver/email/index.ts +0 -155
  210. package/src/devserver/email/providers.ts +0 -131
  211. package/src/devserver/email/status.ts +0 -23
  212. package/src/devserver/email/validate.ts +0 -37
  213. package/src/devserver/email/wire.ts +0 -55
  214. package/src/devserver/http/cache.ts +0 -0
  215. package/src/devserver/http/envelope.ts +0 -157
  216. package/src/devserver/http/proxy.ts +0 -172
  217. package/src/devserver/http/runtime.ts +0 -387
  218. package/src/devserver/index.ts +0 -43
  219. package/src/devserver/production-ipc.ts +0 -63
  220. package/src/devserver/production-worker.ts +0 -83
  221. package/src/devserver/production.ts +0 -718
  222. package/src/devserver/runtime/crypto.ts +0 -587
  223. package/src/devserver/runtime/host.ts +0 -427
  224. package/src/devserver/runtime/module.ts +0 -442
  225. package/src/devserver/server.ts +0 -251
  226. package/src/devserver/ssr.ts +0 -208
  227. package/src/devserver/stream/catalog.ts +0 -106
  228. package/src/devserver/stream/host.ts +0 -42
  229. package/src/devserver/stream/index.ts +0 -301
  230. package/src/devserver/stream/manager.ts +0 -167
  231. package/src/devserver/stream/router.ts +0 -101
  232. package/src/devserver/stream/wire.ts +0 -58
  233. package/src/devserver/stream/ws.ts +0 -78
  234. package/src/devserver/wasm/sections.ts +0 -59
  235. package/src/devserver/wasm/surface.ts +0 -115
  236. package/src/io/FastMap.ts +0 -151
  237. package/src/io/FastSet.ts +0 -113
  238. package/src/io/codec.ts +0 -362
  239. package/src/io/index.ts +0 -10
  240. package/src/io/lengths.ts +0 -14
  241. package/src/io/types.ts +0 -19
  242. package/src/logger/index.ts +0 -22
  243. package/src/shared/index.ts +0 -46
  244. package/test/analytics-dev.test.ts +0 -113
  245. package/test/assembly/aspect-shim.ts +0 -33
  246. package/test/assembly/cookie.spec.ts +0 -303
  247. package/test/assembly/example.spec.ts +0 -22
  248. package/test/assembly/ssr.spec.ts +0 -95
  249. package/test/assembly.test.ts +0 -59
  250. package/test/auth-emails.test.ts +0 -118
  251. package/test/built-ssr.test.ts +0 -148
  252. package/test/channel.test.ts +0 -21
  253. package/test/config-host-port.test.ts +0 -59
  254. package/test/configure.test.ts +0 -94
  255. package/test/daemon-build.test.ts +0 -278
  256. package/test/daemon-catalog.test.ts +0 -349
  257. package/test/daemon-emulation.test.ts +0 -256
  258. package/test/db.test.ts +0 -0
  259. package/test/devserver-database.test.ts +0 -1102
  260. package/test/devserver-email.test.ts +0 -306
  261. package/test/devserver-pqauth.test.ts +0 -93
  262. package/test/devserver-secrets.test.ts +0 -63
  263. package/test/devserver.test.ts +0 -259
  264. package/test/doctor.test.ts +0 -298
  265. package/test/dom/Image.test.tsx +0 -144
  266. package/test/dom/Link.test.tsx +0 -67
  267. package/test/dom/NavLink.test.tsx +0 -37
  268. package/test/dom/Script.test.tsx +0 -48
  269. package/test/dom/action.test.tsx +0 -146
  270. package/test/dom/error-overlay.test.tsx +0 -44
  271. package/test/dom/loader.test.tsx +0 -121
  272. package/test/dom/navigation.test.ts +0 -59
  273. package/test/dom/revalidate.test.tsx +0 -38
  274. package/test/dom/route-head.test.tsx +0 -56
  275. package/test/dom/router-loading.test.tsx +0 -44
  276. package/test/dom/scroll.test.ts +0 -56
  277. package/test/dom/slot.test.tsx +0 -131
  278. package/test/dom/use-metadata.test.tsx +0 -58
  279. package/test/dom/view-transitions.test.tsx +0 -53
  280. package/test/dynamic-routing.test.ts +0 -132
  281. package/test/email-console.test.ts +0 -86
  282. package/test/email-preview.test.ts +0 -73
  283. package/test/emails.test.ts +0 -58
  284. package/test/errors.test.ts +0 -21
  285. package/test/example-guestbook.test.ts +0 -123
  286. package/test/features.test.ts +0 -149
  287. package/test/fixtures/bignum-wire/spec.ts +0 -25
  288. package/test/fixtures/daemon-app.ts +0 -65
  289. package/test/fixtures/stream-echo.ts +0 -26
  290. package/test/fixtures/stream-gate.ts +0 -28
  291. package/test/fixtures/stream-trap.ts +0 -18
  292. package/test/fixtures/stream-typed.ts +0 -41
  293. package/test/fonts.test.ts +0 -28
  294. package/test/global-setup.ts +0 -17
  295. package/test/head.test.ts +0 -42
  296. package/test/image-blur.test.ts +0 -50
  297. package/test/io.test.ts +0 -117
  298. package/test/metadata.test.ts +0 -40
  299. package/test/navlink.test.ts +0 -28
  300. package/test/pages.test.ts +0 -105
  301. package/test/placeholder.test.ts +0 -9
  302. package/test/pqauth-e2e.test.ts +0 -216
  303. package/test/pqauth-email.test.ts +0 -801
  304. package/test/prerender.test.ts +0 -116
  305. package/test/prettier-plugin.test.ts +0 -46
  306. package/test/routes.test.ts +0 -76
  307. package/test/rpc-bignum-wire.test.ts +0 -164
  308. package/test/rpc-dispatch.test.ts +0 -132
  309. package/test/rpc-kinds.test.ts +0 -18
  310. package/test/rpc.test.ts +0 -66
  311. package/test/search.test.ts +0 -114
  312. package/test/seo.test.ts +0 -175
  313. package/test/slot-layouts.test.ts +0 -69
  314. package/test/sri.test.ts +0 -129
  315. package/test/ssg.test.ts +0 -36
  316. package/test/ssr-hydration.test.tsx +0 -122
  317. package/test/ssr-render.test.ts +0 -197
  318. package/test/ssr-template.test.tsx +0 -482
  319. package/test/stream-emulation.test.ts +0 -479
  320. package/test/ui.test.ts +0 -40
  321. package/test/update.test.ts +0 -58
  322. package/test/validate.test.ts +0 -42
  323. package/test/version-check.test.ts +0 -130
  324. package/tests/data-parity/generated-parity.ts +0 -99
  325. package/tests/data-parity/parity.ts +0 -80
  326. package/tests/data-parity/spec.ts +0 -46
@@ -1,344 +0,0 @@
1
- # Metadata and SEO
2
-
3
- Metadata is the information in a page's `<head>`: its title, its description, and the tags that control how it looks when shared on social media or listed by a search engine. toiljs lets you set all of it per route, and bakes it into real HTML so crawlers see it even without running your JavaScript.
4
-
5
- ## The quick version
6
-
7
- For most pages, `export const metadata` from the route file. That is it:
8
-
9
- ```tsx
10
- // client/routes/features/seo.tsx
11
- export const metadata: Toil.Metadata = {
12
- title: 'useReducer | React Hooks',
13
- description: 'Manage complex state transitions with a reducer function.',
14
- keywords: ['react', 'hooks', 'useReducer'],
15
- canonical: 'https://example.com/features/seo',
16
- openGraph: {
17
- title: 'useReducer | React Hooks',
18
- description: 'Manage complex state transitions with a reducer.',
19
- type: 'website',
20
- },
21
- };
22
-
23
- export default function SeoDemo() {
24
- return <main><h1>Route metadata</h1></main>;
25
- }
26
- ```
27
-
28
- The router applies this before the page paints (so the tab title updates with no flicker), and the build bakes it into the page's static HTML so search engines and link-preview bots read it directly.
29
-
30
- ## The `Metadata` fields
31
-
32
- `Toil.Metadata` maps friendly fields onto the right `<meta>` and `<link>` tags for you:
33
-
34
- | Field | Becomes |
35
- | --- | --- |
36
- | `title` | The document `<title>`. |
37
- | `description` | `<meta name="description">`. |
38
- | `keywords` | `<meta name="keywords">` (an array is joined with commas). |
39
- | `canonical` | `<link rel="canonical">`. |
40
- | `robots` | `<meta name="robots">`, e.g. `'noindex, nofollow'`. |
41
- | `themeColor` | `<meta name="theme-color">` (the accent color of some link embeds). |
42
- | `openGraph` | The `og:*` tags (title, description, type, url, image, siteName). |
43
- | `meta` | Escape hatch: extra raw `<meta>` tags. |
44
- | `link` | Escape hatch: extra raw `<link>` tags. |
45
-
46
- Open Graph (the `og:*` tags) is the shared standard that Facebook, Discord, Slack, LinkedIn, and iMessage read to build a link preview card. Set `openGraph.image` (an absolute URL, ideally at least 1200 by 630 pixels) to control the preview picture:
47
-
48
- ```tsx
49
- export const metadata: Toil.Metadata = {
50
- title: 'Our launch',
51
- description: 'Read the announcement.',
52
- openGraph: {
53
- title: 'Our launch',
54
- description: 'Read the announcement.',
55
- type: 'article',
56
- image: 'https://example.com/og/launch.png',
57
- },
58
- };
59
- ```
60
-
61
- ## Dynamic metadata: `generateMetadata`
62
-
63
- When the title depends on the URL or on fetched data (a blog post's title, a product name), export `generateMetadata` instead of a static object. It receives the route params, the query, and the route loader's data, and returns a `Metadata`:
64
-
65
- ```tsx
66
- // client/routes/blog/[id].tsx
67
- export const generateMetadata: Toil.GenerateMetadata = ({ params }) => ({
68
- title: `Blog post ${params.id}`,
69
- description: `Reading blog post ${params.id}.`,
70
- });
71
- ```
72
-
73
- Now `/blog/42` sets the tab to "Blog post 42". If your route has a `loader`, its data is passed in as `data`, so you can title a page from the content it loaded:
74
-
75
- ```tsx
76
- export const loader = async ({ params }: Toil.LoaderArgs) =>
77
- Server.REST.blog.get({ params: { id: params.id } });
78
-
79
- export const generateMetadata: Toil.GenerateMetadata = ({ data }) => ({
80
- title: data.title,
81
- description: data.excerpt,
82
- });
83
- ```
84
-
85
- ## Imperative and stateful head: `useHead`, `useTitle`, `<Head>`
86
-
87
- Sometimes the head depends on component state, not on the route. For that, set it from inside a component with `Toil.useHead`, `Toil.useTitle`, or the declarative `<Toil.Head>`. These apply for the component's lifetime and revert when it unmounts:
88
-
89
- ```tsx
90
- export default function HeadDemo() {
91
- const [count, setCount] = useState(0);
92
-
93
- // The tab title updates every render as count changes.
94
- Toil.useTitle(`Clicked ${count} times`);
95
-
96
- Toil.useHead({
97
- meta: [{ name: 'description', content: `Clicked ${count} times.` }],
98
- });
99
-
100
- return <button onClick={() => setCount((c) => c + 1)}>Clicked {count}</button>;
101
- }
102
- ```
103
-
104
- The declarative form renders nothing and is equivalent:
105
-
106
- ```tsx
107
- <Toil.Head
108
- title="Blog"
109
- meta={[{ name: 'description', content: 'Latest posts' }]}
110
- />
111
- ```
112
-
113
- Use `useHead`/`useTitle` when the value is dynamic or lives in component state; use the `metadata` export when it is a static property of the route.
114
-
115
- ### Applying a whole `Metadata` object from a component: `useMetadata`
116
-
117
- `useHead` takes raw `<meta>` and `<link>` tags. When you would rather pass the same friendly `Metadata` shape you use in a route's `metadata` export (with `title`, `openGraph`, `keywords`, and the rest), use `Toil.useMetadata` from inside any component. It applies for that component's lifetime and reverts on unmount, exactly like `useHead`. This is the tool for content that is not itself a route file: a reusable article component, a widget, a search-result view.
118
-
119
- ```tsx
120
- export default function Article({ post }: { post: Post }) {
121
- Toil.useMetadata({
122
- title: post.title,
123
- description: post.excerpt,
124
- openGraph: { type: 'article', title: post.title, image: post.cover },
125
- });
126
-
127
- return <article>{/* ... */}</article>;
128
- }
129
- ```
130
-
131
- There is a declarative twin, `<Toil.Metadata title="..." openGraph={...} />`, which renders nothing (the component-level counterpart of a route's `metadata` export). A route's own `metadata` export is still merged last (highest priority), so it wins for the keys it sets, and `useMetadata` fills in for routes that declare none.
132
-
133
- > **Advanced.** Under the hood, `Toil.resolveMetadata(metadata)` is the pure function that expands a `Metadata` object into concrete `<meta>`/`<link>` tags (a `HeadSpec`), and `Toil.mergeHead(specs)` is the pure merge that resolves all active head contributions into the final result (the last `title` wins, `meta` dedupes by `name`/`property`, `link` by `rel` + `href`). You rarely call these directly, but they are exported for tooling and tests.
134
-
135
- ## How the pieces combine
136
-
137
- Multiple things can contribute to the head at once: your site-wide defaults, a layout, and the page. They merge by key, with a clear priority. Later, more specific contributions win per key, and anything left unset falls through to a broader default:
138
-
139
- ```mermaid
140
- flowchart TB
141
- A["Site-wide defaults<br/>client.seo config"] --> M{"merge by key"}
142
- B["Root layout &lt;Toil.Head&gt;<br/>(fallback title, description)"] --> M
143
- C["Component useHead / useTitle"] --> M
144
- D["Route metadata / generateMetadata<br/>(highest priority)"] --> M
145
- M --> R["The page's final &lt;head&gt;"]
146
- ```
147
-
148
- - A **root layout** is the natural home for site-wide fallbacks (a default title and description for any page that sets none):
149
-
150
- ```tsx
151
- // client/layout.tsx
152
- <Toil.Head
153
- title="My Site"
154
- meta={[{ name: 'description', content: 'Planet-scale apps.' }]}
155
- />
156
- ```
157
-
158
- - A **route's `metadata`** overrides those defaults for the keys it sets, while the layout still fills in everything the route leaves unset. So a page can set just a `title` and inherit the site description.
159
-
160
- The rule of thumb: put fallbacks in the layout, put the specifics on the route.
161
-
162
- ## Build-time SEO for the whole site
163
-
164
- Beyond per-route metadata, toiljs generates site-level SEO assets at build time from a `client.seo` block in `toil.config.ts`. These are baked into the HTML `<head>` and written as files, so JavaScript-less crawlers and AI bots get correct information:
165
-
166
- ```ts
167
- // toil.config.ts
168
- import { defineConfig } from 'toiljs/compiler';
169
-
170
- export default defineConfig({
171
- client: {
172
- seo: {
173
- url: 'https://example.com', // required for canonical/OG urls, sitemap
174
- title: 'My Site',
175
- description: 'Planet-scale apps from a single repo.',
176
- openGraph: {
177
- type: 'website',
178
- siteName: 'My Site',
179
- image: 'https://example.com/og.png',
180
- },
181
- twitter: { card: 'summary_large_image', site: '@mysite' },
182
- robots: { ai: 'allow' }, // allow or disallow known AI crawlers
183
- llms: { instructions: 'Docs live at /docs.' },
184
- jsonLd: { '@context': 'https://schema.org', '@type': 'WebSite', name: 'My Site' },
185
- },
186
- },
187
- });
188
- ```
189
-
190
- From this one block, the build:
191
-
192
- - bakes the default `<title>`, `description`, Open Graph, Twitter card, and JSON-LD structured data into every page's HTML;
193
- - overlays each route's own `metadata` on top and points that route's canonical and `og:url` at its own URL;
194
- - generates `robots.txt` (with directives for AI crawlers like GPTBot and ClaudeBot), `sitemap.xml` (from your static routes), and `llms.txt` (a guidance file for AI crawlers).
195
-
196
- You get correct, per-page SEO in the raw HTML with almost no manual tag writing. Confirm it with "View source" on a built page: the real title and tags are right there, not injected later by JavaScript.
197
-
198
- ### The complete `client.seo` reference
199
-
200
- Every field `client.seo` accepts is below. All of them are optional, and everything you set becomes a baked-in `<head>` tag or a generated file (`robots.txt`, `sitemap.xml`, `llms.txt`).
201
-
202
- **Top level:**
203
-
204
- | Field | Type | What it does |
205
- | --- | --- | --- |
206
- | `url` | `string` | Absolute site base URL, e.g. `'https://example.com'`. Unlocks `sitemap.xml`, the canonical `<link>`, and absolute Open Graph / Twitter URLs. |
207
- | `title` | `string` | Default document `<title>`. |
208
- | `description` | `string` | Default `<meta name="description">`. |
209
- | `robotsMeta` | `string` | Default `<meta name="robots">`, e.g. `'index, follow'`. (This is the meta tag; the `robots` field below is the separate `robots.txt` file.) |
210
- | `themeColor` | `string` | `<meta name="theme-color">`, also the accent color of some Discord / Slack link embeds. |
211
- | `openGraph` | object | Open Graph (`og:*`) defaults, see below. |
212
- | `twitter` | object | Twitter / X card, see below. |
213
- | `facebook` | `{ appId?: string }` | `appId` renders `<meta property="fb:app_id">`. Open Graph covers the rest of the Facebook card. |
214
- | `preconnect` | `string[]` | Origins to `<link rel="preconnect">` (early connection hints). |
215
- | `dnsPrefetch` | `string[]` | Origins to `<link rel="dns-prefetch">`. |
216
- | `jsonLd` | object or object[] | JSON-LD structured data injected as `<script type="application/ld+json">`. Pass an array to include several nodes (they are serialized into one `<script>` as a JSON array). |
217
- | `robots` | object or `false` | `robots.txt` generation, see below. `false` skips the file. |
218
- | `sitemap` | `boolean` | `sitemap.xml` generation. On by default when `url` is set; `false` skips it. |
219
- | `llms` | object or `boolean` | `llms.txt` (AI-crawler guidance) generation. `false` skips it, `true` or an object configures it. |
220
-
221
- **`openGraph`** (the `og:*` tags; `title` and `description` fall back to the top-level values):
222
-
223
- | Field | Type | Renders |
224
- | --- | --- | --- |
225
- | `title` | `string` | `og:title` |
226
- | `description` | `string` | `og:description` |
227
- | `type` | `string` | `og:type`, e.g. `'website'` or `'article'` (defaults to `'website'`). |
228
- | `siteName` | `string` | `og:site_name` |
229
- | `locale` | `string` | `og:locale`, e.g. `'en_US'`. |
230
- | `image` | `string` | `og:image`, the preview picture (absolute URL, ideally at least 1200 by 630 pixels). |
231
- | `imageAlt` | `string` | `og:image:alt` |
232
- | `imageWidth` | `number` | `og:image:width` in pixels (lets Facebook / LinkedIn render without a re-fetch). |
233
- | `imageHeight` | `number` | `og:image:height` in pixels. |
234
- | `imageType` | `string` | `og:image:type`, e.g. `'image/png'`. |
235
-
236
- **`twitter`** (the Twitter / X card; unset fields fall back to the Open Graph or top-level values):
237
-
238
- | Field | Type | Renders |
239
- | --- | --- | --- |
240
- | `card` | `string` | `twitter:card`: `'summary'` or `'summary_large_image'` (defaults by whether an image is present). |
241
- | `site` | `string` | `twitter:site`, the site's `@handle`. |
242
- | `creator` | `string` | `twitter:creator`, the author's `@handle`. |
243
- | `title` | `string` | `twitter:title` (falls back to `openGraph.title` / `title`). |
244
- | `description` | `string` | `twitter:description` (falls back to `openGraph.description` / `description`). |
245
- | `image` | `string` | `twitter:image` (falls back to `openGraph.image`). |
246
- | `imageAlt` | `string` | `twitter:image:alt` (falls back to `openGraph.imageAlt`). |
247
-
248
- **`robots`** (the `robots.txt` file; set `robots: false` to skip it):
249
-
250
- | Field | Type | What it does |
251
- | --- | --- | --- |
252
- | `rules` | `RobotsRule[]` | Custom `User-agent` groups. Each rule is `{ userAgent?: string \| string[], allow?: string[], disallow?: string[] }`. Defaults to one group allowing everything (`User-agent: *`, `Allow: /`). |
253
- | `ai` | `'allow'` or `'disallow'` | How to treat known AI crawlers (GPTBot, ClaudeBot, Google-Extended, and more). Default `'allow'`. |
254
- | `sitemap` | `string` | Explicit `Sitemap:` line (defaults to `<url>/sitemap.xml` when `url` is set). |
255
-
256
- **`llms`** (the `llms.txt` guidance file; set `llms: false` to skip it, `llms: true` for defaults):
257
-
258
- | Field | Type | What it does |
259
- | --- | --- | --- |
260
- | `title` | `string` | The file's heading (falls back to `seo.title`). |
261
- | `summary` | `string` | The summary blockquote (falls back to `seo.description`). |
262
- | `instructions` | `string` | Free-form guidance for AI / LLM crawlers. |
263
- | `pages` | `LlmsPage[]` | Key pages, each `{ title: string, url: string, description?: string }`. Defaults to the site's static routes. |
264
-
265
- A fully worked config touching most of these fields:
266
-
267
- ```ts
268
- // toil.config.ts
269
- import { defineConfig } from 'toiljs/compiler';
270
-
271
- export default defineConfig({
272
- client: {
273
- seo: {
274
- url: 'https://example.com', // base URL: unlocks sitemap, canonical, absolute OG/Twitter URLs
275
- title: 'My Site', // default <title>
276
- description: 'Planet-scale apps.', // default <meta name="description">
277
- robotsMeta: 'index, follow', // default <meta name="robots">
278
- themeColor: '#0b0b0f', // <meta name="theme-color">
279
-
280
- openGraph: {
281
- type: 'website', // og:type
282
- siteName: 'My Site', // og:site_name
283
- locale: 'en_US', // og:locale
284
- image: 'https://example.com/og.png', // og:image (absolute, ideally 1200x630)
285
- imageAlt: 'My Site preview', // og:image:alt
286
- imageWidth: 1200, // og:image:width
287
- imageHeight: 630, // og:image:height
288
- imageType: 'image/png', // og:image:type
289
- },
290
-
291
- twitter: {
292
- card: 'summary_large_image', // twitter:card
293
- site: '@mysite', // twitter:site
294
- creator: '@ada', // twitter:creator
295
- // title / description / image / imageAlt fall back to openGraph + top level
296
- },
297
-
298
- facebook: { appId: '1234567890' }, // <meta property="fb:app_id">
299
-
300
- preconnect: ['https://cdn.example.com'], // <link rel="preconnect">
301
- dnsPrefetch: ['https://analytics.example.com'], // <link rel="dns-prefetch">
302
-
303
- robots: {
304
- ai: 'allow', // known AI crawlers: 'allow' | 'disallow'
305
- rules: [ // custom User-agent groups for robots.txt
306
- { userAgent: '*', allow: ['/'], disallow: ['/admin'] },
307
- ],
308
- // sitemap: 'https://example.com/sitemap.xml', // explicit line (auto from url otherwise)
309
- },
310
- sitemap: true, // generate sitemap.xml (on by default when url is set)
311
-
312
- llms: { // llms.txt (AI-crawler guidance)
313
- title: 'My Site',
314
- summary: 'Planet-scale apps from a single repo.',
315
- instructions: 'Docs live at /docs.',
316
- // pages: [{ title: 'Docs', url: 'https://example.com/docs', description: 'Guides' }],
317
- },
318
-
319
- jsonLd: [ // array = several nodes in one <script>
320
- { '@context': 'https://schema.org', '@type': 'WebSite', name: 'My Site' },
321
- { '@context': 'https://schema.org', '@type': 'Organization', name: 'My Site' },
322
- ],
323
- },
324
- },
325
- });
326
- ```
327
-
328
- ## Per-request titles on server-rendered pages
329
-
330
- For a page that is server-rendered (`export const ssr = true`), the backend can set a fresh `<title>` for each individual request (for example, a search page titled with the query the visitor typed). The edge splices that per-request title into the document before sending it, so the correct title is in the very first byte of HTML. This is a server-side API used in your `render` function; the frontend `metadata` above covers everything else. See [Rendering and SSR](./rendering.md) for how SSR pages are assembled.
331
-
332
- ## Gotchas
333
-
334
- - **`generateMetadata` needs the data available.** It runs with the route loader's data, so it can only use what the loader returns. Fetch what the title needs in the loader.
335
- - **Open Graph images must be absolute URLs.** A relative `/og.png` will not resolve for an external crawler. Use the full `https://...` URL (set `seo.url` and it can build them for you).
336
- - **`seo.url` unlocks the site-level assets.** `sitemap.xml`, canonical links, and absolute OG urls all need the site's base `url`. Set it once in the config.
337
- - **Static export vs runtime.** The `metadata` export is baked into HTML at build (great for crawlers) *and* applied at runtime on navigation. `useHead` runs only at runtime; a crawler that does not execute JS will not see it. Prefer the `metadata` export for anything that matters for SEO.
338
-
339
- ## Related
340
-
341
- - [Rendering and SSR](./rendering.md): how the baked head and SSR title reach the browser.
342
- - [Images](./images.md): producing the `og:image` for share previews.
343
- - [Routing](./routing.md): where `metadata` and `generateMetadata` live in a route file.
344
- - [Configuration](../concepts/config.md): the full `toil.config.ts` reference.
@@ -1,296 +0,0 @@
1
- # Navigation
2
-
3
- Once your files are turned into URLs (see [Routing](./routing.md)), you need a way to move between them. This page is about that half: the links, hooks, and functions that navigate a user from one page to the next without a full reload, keep the current section highlighted, prefetch what is coming, and restore scroll. Everything here lives on the global `Toil` object, so route files need no imports for the common cases.
4
-
5
- Routing is "file to URL"; navigation is "get me to that URL". If you are looking for how a file becomes a page, or for dynamic params and layouts, that is the [Routing](./routing.md) page.
6
-
7
- ## `Toil.Link`
8
-
9
- `Toil.Link` is the client-side replacement for a plain `<a>`. It navigates in place (no full page reload), and it prefetches the target route's chunk on hover or focus, so the click feels instant:
10
-
11
- ```tsx
12
- <Toil.Link href="/about">About</Toil.Link>
13
- ```
14
-
15
- `Link` accepts every standard anchor attribute (`className`, `style`, `target`, `rel`, `download`, `referrerPolicy`, `ref`, `data-*`, `aria-*`, event handlers, and so on), plus a few toiljs controls:
16
-
17
- | Prop | Type | Default | What it does |
18
- | --- | --- | --- | --- |
19
- | `href` | `Href` | (required) | Destination, typed to your project's real routes (a typo is a compile error). |
20
- | `replace` | `boolean` | `false` | Replace the current history entry instead of pushing a new one. |
21
- | `scroll` | `boolean` | `true` | Scroll to the top after navigating. `false` keeps the current position. |
22
- | `prefetch` | `boolean` | `true` | Prefetch the route chunk on hover/focus. `false` opts this link out. |
23
-
24
- ### When `Link` does not intercept
25
-
26
- `Link` is deliberate about when to hand a click back to the browser. It only intercepts a plain, same-origin, left-click. All of these fall through to native browser behavior instead:
27
-
28
- - **External URLs** (a different origin), and opaque targets like `mailto:` or `tel:`.
29
- - **`target` other than `_self`** (for example `target="_blank"`, a new tab).
30
- - **`download`** links.
31
- - **In-page `#hash`-only** links.
32
- - **Modified clicks**: middle-click or any click with Ctrl, Cmd (Meta), Shift, or Alt held (so "open in new tab" keeps working).
33
-
34
- Because of this, you can point a `Link` at an external site or a download and it behaves exactly like an `<a>` would, no special-casing on your part. For genuinely external links a plain `<a>` is still fine; reach for `Link` when the target is one of your own routes.
35
-
36
- ## `Toil.NavLink` and active state
37
-
38
- `Toil.NavLink` is a `Link` that knows whether it points at the current page. When active it adds a class (`active` by default) and sets `aria-current="page"`, which is exactly what a navigation bar wants for highlighting the current section. It inherits Link's full anchor API and its prefetching.
39
-
40
- On top of `LinkProps` it adds:
41
-
42
- | Prop | Type | Default | What it does |
43
- | --- | --- | --- | --- |
44
- | `end` | `boolean` | `false` | Require an exact match. Without it, a parent link is active for its sub-paths. |
45
- | `activeClassName` | `string` | `'active'` | The class added when active (used with a string `className`). |
46
- | `className` | `string \| (state) => string \| undefined` | (none) | A string, or a function of `{ isActive }`. |
47
- | `style` | `CSSProperties \| (state) => CSSProperties \| undefined` | (none) | A style object, or a function of `{ isActive }`. |
48
- | `children` | `ReactNode \| (state) => ReactNode` | (none) | Content, or a function of `{ isActive }`. |
49
-
50
- A simple nav bar with string classes:
51
-
52
- ```tsx
53
- // client/components/Nav.tsx
54
- export default function Nav() {
55
- return (
56
- <nav>
57
- <Toil.NavLink href="/" end>Home</Toil.NavLink>
58
- <Toil.NavLink href="/blog">Blog</Toil.NavLink>
59
- <Toil.NavLink href="/about" activeClassName="is-current">About</Toil.NavLink>
60
- </nav>
61
- );
62
- }
63
- ```
64
-
65
- By default a parent link stays active for its sub-paths, so `/blog` is active on `/blog`, `/blog/42`, and `/blog/42/edit`. Pass `end` to require an exact match. This matters most for the home link: `/` would otherwise be active on every page, so `Home` almost always wants `end`.
66
-
67
- The function forms let the active state drive `className`, `style`, or `children` directly. Each receives a `{ isActive }` object:
68
-
69
- ```tsx
70
- <Toil.NavLink href="/blog" className={({ isActive }) => (isActive ? 'tab on' : 'tab')}>
71
- {({ isActive }) => <span>{isActive ? '• ' : ''}Blog</span>}
72
- </Toil.NavLink>
73
- ```
74
-
75
- ## Navigating in code
76
-
77
- Not every navigation is a click. After a form submit, a login, or a `fetch`, you often want to move the user yourself. There are three ways, from smallest to fullest.
78
-
79
- ### `Toil.useNavigate()`
80
-
81
- The hook returns the bare `navigate(href, options)` function:
82
-
83
- ```tsx
84
- export default function Login() {
85
- const navigate = Toil.useNavigate();
86
- return (
87
- <button onClick={() => navigate('/dashboard', { replace: true })}>
88
- Continue
89
- </button>
90
- );
91
- }
92
- ```
93
-
94
- The options are `NavigateOptions`, the same two controls `Link` exposes:
95
-
96
- | Option | Type | Default | What it does |
97
- | --- | --- | --- | --- |
98
- | `replace` | `boolean` | `false` | Replace the current history entry instead of pushing. |
99
- | `scroll` | `boolean` | `true` | Scroll to top after navigating. `false` keeps the position. |
100
-
101
- ### `Toil.navigate` (outside React)
102
-
103
- The exact same function is available free of any hook as `Toil.navigate(href, options)`. Use it in code that is not a React component: a plain event handler, a utility module, or right after a `fetch` resolves:
104
-
105
- ```tsx
106
- async function saveDraft(draft: Draft) {
107
- await Server.REST.posts.create({ body: draft });
108
- Toil.navigate('/posts');
109
- }
110
- ```
111
-
112
- `Toil.back()`, `Toil.forward()`, and `Toil.refresh()` are the history counterparts, also callable from anywhere: `back` and `forward` step through history, and `refresh` re-renders the current route (re-running its loader for the current URL).
113
-
114
- ### `Toil.useRouter()`
115
-
116
- For a full imperative handle, `useRouter()` returns a `RouterInstance`:
117
-
118
- ```tsx
119
- export default function PostActions() {
120
- const router = Toil.useRouter();
121
- return (
122
- <>
123
- <button onClick={() => router.push('/blog/new')}>New post</button>
124
- <button onClick={() => router.back()}>Back</button>
125
- <button onClick={() => router.revalidate()}>Refresh data</button>
126
- </>
127
- );
128
- }
129
- ```
130
-
131
- | Method | What it does |
132
- | --- | --- |
133
- | `push(href, options?)` | Navigate to `href`, pushing a new history entry (or replacing with `{ replace: true }`). |
134
- | `replace(href)` | Navigate to `href`, replacing the current history entry. |
135
- | `back()` | Go back one history entry. |
136
- | `forward()` | Go forward one history entry. |
137
- | `refresh()` | Re-render the current route and re-run its loader (clears all cached loader data). |
138
- | `revalidate(href?)` | Invalidate cached loader data and re-render so it refetches. No argument targets the active route; pass an `href` for a specific route. Use after a mutation. |
139
- | `prefetch(href)` | Warm a route's chunk ahead of navigation. |
140
-
141
- Reach for `revalidate()` after a write (you changed some data and want the current page's loader to refetch), and `refresh()` when you want a clean re-run of everything. `push`/`replace` are the imperative twins of a `Link` click.
142
-
143
- ## Typed hrefs and the `href()` escape hatch
144
-
145
- Every `href` you pass to `Toil.Link`, `NavLink`, `navigate`, or `router.push` is type-checked against your project's real routes. The compiler scans `client/routes/` and generates a `toil-routes.d.ts` that narrows the `Href` type to the union of your actual paths, so a typo is a compile error before you run the app:
146
-
147
- ```tsx
148
- <Toil.Link href="/abuot">About</Toil.Link>
149
- // ^ Type error: "/abuot" is not assignable to type 'Href'. (No such route.)
150
- ```
151
-
152
- Dynamic routes appear in that union as template-literal types, so a file at `client/routes/blog/[id].tsx` contributes `` `/blog/${string}` `` and a single interpolation still checks:
153
-
154
- ```tsx
155
- <Toil.Link href={`/blog/${post.id}`}>Read</Toil.Link> // fine
156
- ```
157
-
158
- When a URL is assembled from several data pieces (or from values TypeScript cannot prove the shape of), it is typed as a plain `string`, and `string` is not assignable to `Href`. That is when you reach for `Toil.href()`, the escape hatch that asserts a runtime string is a valid href:
159
-
160
- ```tsx
161
- const path = `/${product.category}/${product.slug}`; // string
162
- Toil.navigate(Toil.href(path)); // asserted valid
163
- <Toil.Link href={Toil.href(path)}>Open</Toil.Link>;
164
- ```
165
-
166
- `href()` is a pure type assertion (it returns the string unchanged), so use it only when the type-check is genuinely in your way, not to paper over a real typo. Before the routes are generated (a fresh project, the first build), `Href` is just `string`, so nothing complains until `toil-routes.d.ts` exists.
167
-
168
- ## Reading the current location
169
-
170
- A handful of hooks let a component read where it is. Each re-reads on every navigation, so a component using one re-renders when the location changes:
171
-
172
- | Hook | Returns |
173
- | --- | --- |
174
- | `Toil.usePathname()` | The current pathname, e.g. `"/blog/42"`. |
175
- | `Toil.useLocation()` | The current pathname (an alias of `usePathname()`). |
176
- | `Toil.useParams<T>()` | The dynamic route params, e.g. `{ id }` for `/blog/[id]`. |
177
- | `Toil.useSearchParams()` | The query string as a `URLSearchParams`. |
178
- | `Toil.useNavigationPending()` | `true` while a navigation is in flight (started but not committed). |
179
-
180
- `useNavigationPending()` is what you wire a top loading bar to. It flips to `true` when a navigation begins and back to `false` once the new route commits:
181
-
182
- ```tsx
183
- // client/components/ProgressBar.tsx
184
- export default function ProgressBar() {
185
- const pending = Toil.useNavigationPending();
186
- return <div className="progress" data-active={pending} />;
187
- }
188
- ```
189
-
190
- `useSearchParams()` gives you a live `URLSearchParams`, so a filtered list reads its state from the URL:
191
-
192
- ```tsx
193
- export default function Results() {
194
- const params = Toil.useSearchParams();
195
- const q = params.get('q') ?? '';
196
- return <p>Results for {q}</p>;
197
- }
198
- ```
199
-
200
- ## Prefetching
201
-
202
- toiljs prefetches routes before you navigate to them, so a click resolves with nothing left to download. There are two intents, both automatic:
203
-
204
- - **Hover / focus intent.** When you hover or focus a link that points at a known internal route, toiljs warms both its route chunk and its loader data, so the actual click can commit right away.
205
- - **Viewport intent.** As a link scrolls into view (or within about 200px of it), its route chunk is warmed. Links added later by client navigation are picked up automatically.
206
-
207
- Prefetching is best-effort and cheap: each route loads at most once, a failed prefetch is forgotten (so the real navigation can retry and surface the error), and new-tab / download / opted-out links are skipped. It is also **skipped entirely when the browser signals data-saver** (or reports a 2g-class connection), so you never spend a metered user's bandwidth on speculation.
208
-
209
- Opt a single link out with `prefetch={false}`, which emits a `data-no-prefetch` attribute the prefetcher respects:
210
-
211
- ```tsx
212
- <Toil.Link href="/huge-report" prefetch={false}>Annual report</Toil.Link>
213
- ```
214
-
215
- You can also warm a route yourself, for example right before an imperative `navigate`, with the standalone `Toil.prefetch(href)`:
216
-
217
- ```tsx
218
- <button
219
- onPointerEnter={() => Toil.prefetch('/dashboard')}
220
- onClick={() => Toil.navigate('/dashboard')}>
221
- Open dashboard
222
- </button>
223
- ```
224
-
225
- ## Scroll restoration
226
-
227
- toiljs manages scroll for you (it switches off the browser's automatic restoration and does the intuitive thing per navigation type):
228
-
229
- - **A push navigation** (a `Link` click, `navigate`, `router.push`) **scrolls to the top** of the new page.
230
- - **Back and forward restore** the scroll position you had on that entry, so returning to a long list lands you where you were.
231
- - **A `#hash` target** scrolls that element into view instead of jumping to the top.
232
-
233
- To keep the current scroll on a push navigation, set `scroll={false}` on the `Link` (or `{ scroll: false }` in `NavigateOptions`). This is handy for tab bars or filters that change the URL but should not yank the viewport:
234
-
235
- ```tsx
236
- <Toil.Link href="/settings/billing" scroll={false}>Billing</Toil.Link>
237
- ```
238
-
239
- ```tsx
240
- navigate('/settings/billing', { scroll: false });
241
- ```
242
-
243
- ## Animated transitions
244
-
245
- Two optional effects can animate navigations. Both are **off by default** and are normally enabled from `toil.config.ts`, not in code:
246
-
247
- ```ts
248
- // toil.config.ts
249
- export default {
250
- client: {
251
- viewTransitions: true, // browser View Transitions API (a crossfade between pages)
252
- transitions: true, // React transition: keep the old page visible while the next loads
253
- },
254
- };
255
- ```
256
-
257
- `viewTransitions` uses the browser's View Transitions API to crossfade the old and new page (and it respects `prefers-reduced-motion`, animating nothing for users who ask for less motion). `transitions` wraps each navigation in a React transition, keeping the current page on screen while the next route's loader runs, instead of showing its `loading.tsx` right away (smoother, but you trade away the immediate loading state).
258
-
259
- Config is the normal way to turn these on. For a manual override at runtime, the setters are `Toil.setViewTransitions(enabled)` and `Toil.setTransitions(enabled)`:
260
-
261
- ```tsx
262
- Toil.setViewTransitions(true);
263
- Toil.setTransitions(false);
264
- ```
265
-
266
- You rarely need the setters; prefer the config keys unless you are toggling an effect dynamically.
267
-
268
- ## Types
269
-
270
- Almost everything on this page is a value on the global `Toil` object, so you call it with no import (`Toil.Link`, `Toil.navigate`, `Toil.useRouter`, and so on). A few of the **types**, though, are not namespaced under `Toil`, so if you want to annotate a prop or a variable you import them from `toiljs/client`:
271
-
272
- ```tsx
273
- import type {
274
- LinkProps,
275
- NavLinkProps,
276
- NavLinkState,
277
- NavigateOptions,
278
- RouterInstance,
279
- } from 'toiljs/client';
280
- ```
281
-
282
- For example, a component that forwards `Link` props, or a helper typed against the router handle:
283
-
284
- ```tsx
285
- import type { RouterInstance } from 'toiljs/client';
286
-
287
- function logoutThenGo(router: RouterInstance) {
288
- router.replace('/login');
289
- }
290
- ```
291
-
292
- ## Related
293
-
294
- - [Routing](./routing.md): how files become URLs, dynamic params, layouts, and templates.
295
- - [Fetching data](./data-fetching.md): loaders, the typed backend clients, forms, and revalidation.
296
- - [Rendering and SSR](./rendering.md): what renders on the server versus the browser, and how hydration fits with client navigation.