@absolutejs/absolute 0.20.0-beta.169 → 0.20.0-beta.170

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 (238) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +232 -0
  3. package/changelog.json +228 -0
  4. package/dist/angular/browser.js +7 -8
  5. package/dist/angular/browser.js.map +6 -5
  6. package/dist/angular/components/core/streamingSlotRegistrar.js +6 -2
  7. package/dist/angular/components/core/streamingSlotRegistry.js +7 -3
  8. package/dist/angular/index.js +555 -156
  9. package/dist/angular/index.js.map +29 -23
  10. package/dist/angular/server.js +555 -156
  11. package/dist/angular/server.js.map +29 -23
  12. package/dist/build/buildWorker.js +14 -8
  13. package/dist/build.js +4949 -1941
  14. package/dist/build.js.map +72 -42
  15. package/dist/cli/{add-7azr0c83.js → add-6a7pjkn9.js} +11 -7
  16. package/dist/cli/{analyze-3cyek6v3.js → analyze-606fshwa.js} +1 -1
  17. package/dist/cli/{build-25rz8v25.js → build-g81xqbb9.js} +1 -1
  18. package/dist/cli/{compile-4jnfxts9.js → compile-9y1w9zpj.js} +3 -3
  19. package/dist/cli/config/server.js +10699 -1538
  20. package/dist/cli/{dev-9tk5hn2e.js → dev-hyeb89hp.js} +5 -1
  21. package/dist/cli/{doctor-4nv2c2j3.js → doctor-h55y9hj1.js} +1 -1
  22. package/dist/cli/{generate-v75p2fct.js → generate-20x1pkf7.js} +4 -4
  23. package/dist/cli/{htmx-427swtdz.js → htmx-6mz2h7k1.js} +2 -2
  24. package/dist/cli/{index-bhbs6d5z.js → index-32ge2w1f.js} +12 -4
  25. package/dist/cli/{index-a4ws2jwd.js → index-3wz3mktm.js} +1 -1
  26. package/dist/cli/{index-b7hjgknf.js → index-9sksd8c8.js} +13 -4
  27. package/dist/cli/{index-eyzwc7gz.js → index-qbrzvmh9.js} +4 -2
  28. package/dist/cli/index-qw98g1p6.js +269 -0
  29. package/dist/cli/{index-434b7j4a.js → index-vtp20gwf.js} +69 -1
  30. package/dist/cli/{index-h7qhz02e.js → index-wk0pyxrg.js} +3 -0
  31. package/dist/cli/{index-k3y8cb6c.js → index-z6f43b20.js} +1 -1
  32. package/dist/cli/index.js +16 -16
  33. package/dist/cli/{islands-8sgky9gh.js → islands-dc54e5y1.js} +1 -1
  34. package/dist/cli/{ls-4m4yqwww.js → ls-b6zz24nt.js} +1 -1
  35. package/dist/cli/{mobile-hcxjr2gf.js → mobile-ajnnvqtv.js} +3 -3
  36. package/dist/cli/prerender-fqpp0m1j.js +10 -0
  37. package/dist/cli/{remove-1ycjx85k.js → remove-t8x1mrxg.js} +4 -4
  38. package/dist/cli/{start-mbaejxhm.js → start-d6p831ny.js} +3 -3
  39. package/dist/cli/{typecheck-dr068pmh.js → typecheck-stbt0qt5.js} +6 -1
  40. package/dist/cli/{workspace-1sycp9g1.js → workspace-61hh6jq1.js} +1 -1
  41. package/dist/client/chunks/index-6b66nzyf.js +381 -0
  42. package/dist/client/chunks/index-6b66nzyf.js.map +11 -0
  43. package/dist/client/chunks/{islands-rc28k5sx.js → islands-s2ta5ajb.js} +5 -2
  44. package/dist/client/chunks/{islands-rc28k5sx.js.map → islands-s2ta5ajb.js.map} +7 -6
  45. package/dist/client/index.js +160 -3
  46. package/dist/client/index.js.map +6 -4
  47. package/dist/client/navigation.js +764 -0
  48. package/dist/client/navigation.js.map +10 -0
  49. package/dist/client/prefetch.js +33 -375
  50. package/dist/client/prefetch.js.map +3 -4
  51. package/dist/core/streamingSlotRegistrar.js +5 -2
  52. package/dist/core/streamingSlotRegistrar.js.map +5 -4
  53. package/dist/core/streamingSlotRegistry.js +5 -2
  54. package/dist/core/streamingSlotRegistry.js.map +5 -4
  55. package/dist/dev/client/cspNonce.ts +28 -0
  56. package/dist/dev/client/handlers/html.ts +4 -3
  57. package/dist/dev/client/handlers/htmx.ts +3 -2
  58. package/dist/dev/client/handlers/svelte.ts +15 -90
  59. package/dist/dev/client/syncDevtools.ts +2 -1
  60. package/dist/dev/serverBootstrap.js +11 -4
  61. package/dist/i18n/index.js +543 -0
  62. package/dist/i18n/index.js.map +15 -0
  63. package/dist/index.js +6339 -2233
  64. package/dist/index.js.map +89 -54
  65. package/dist/islands/index.js +282 -40
  66. package/dist/islands/index.js.map +14 -10
  67. package/dist/mobile/browser.js +7 -2
  68. package/dist/mobile/browser.js.map +5 -4
  69. package/dist/mobile/index.js +7 -2
  70. package/dist/mobile/index.js.map +5 -4
  71. package/dist/mobile/remoteMacAgentEntry.js +1 -1
  72. package/dist/react/components/browser/index.js +94 -4
  73. package/dist/react/components/index.js +150 -10
  74. package/dist/react/components/index.js.map +15 -10
  75. package/dist/react/hooks/index.js +479 -13
  76. package/dist/react/hooks/index.js.map +12 -4
  77. package/dist/react/index.js +769 -166
  78. package/dist/react/index.js.map +32 -21
  79. package/dist/react/router/browser.js +21 -4
  80. package/dist/react/router/browser.js.map +5 -4
  81. package/dist/react/router/index.js +21 -4
  82. package/dist/react/router/index.js.map +5 -4
  83. package/dist/react/server.js +597 -203
  84. package/dist/react/server.js.map +25 -15
  85. package/dist/src/angular/zoneRuntime.d.ts +5 -0
  86. package/dist/src/build/buildReactServerComponents.d.ts +22 -0
  87. package/dist/src/build/downlevelAsyncForZone.d.ts +1 -0
  88. package/dist/src/build/fileRoutes.d.ts +65 -0
  89. package/dist/src/build/fonts/buildFonts.d.ts +25 -0
  90. package/dist/src/build/fonts/fallbackMetrics.d.ts +13 -0
  91. package/dist/src/build/fonts/googleFonts.d.ts +18 -0
  92. package/dist/src/build/fonts/injectFontTags.d.ts +7 -0
  93. package/dist/src/build/fonts/parseFontMetrics.d.ts +4 -0
  94. package/dist/src/build/generateReactIndexes.d.ts +7 -1
  95. package/dist/src/build/htmlIncludes.d.ts +16 -0
  96. package/dist/src/build/inlineSourceMapComment.d.ts +1 -0
  97. package/dist/src/build/scanConventions.d.ts +3 -0
  98. package/dist/src/build/vitals.d.ts +21 -0
  99. package/dist/src/client/formActions.d.ts +29 -0
  100. package/dist/src/client/index.d.ts +2 -0
  101. package/dist/src/client/navigation.d.ts +16 -0
  102. package/dist/src/client/prefetch.d.ts +2 -30
  103. package/dist/src/core/build.d.ts +2 -2
  104. package/dist/src/core/cspNonce.d.ts +12 -0
  105. package/dist/src/core/fileRouteAdapter.d.ts +15 -0
  106. package/dist/src/core/fileRoutes.d.ts +5 -0
  107. package/dist/src/core/formActionFlash.d.ts +25 -0
  108. package/dist/src/core/formActions.d.ts +249 -0
  109. package/dist/src/core/index.d.ts +5 -0
  110. package/dist/src/core/partialPrerender.d.ts +28 -0
  111. package/dist/src/core/partialPrerenderServe.d.ts +24 -0
  112. package/dist/src/core/precompressed.d.ts +11 -1
  113. package/dist/src/core/prerenderNonce.d.ts +9 -0
  114. package/dist/src/core/productionRuntime.d.ts +4 -2
  115. package/dist/src/core/serverOnlyStaticFiles.d.ts +8 -0
  116. package/dist/src/core/staticHtmlNonce.d.ts +2 -0
  117. package/dist/src/core/vitals.d.ts +108 -0
  118. package/dist/src/dev/angular/fastHmrCompiler.d.ts +1 -1
  119. package/dist/src/dev/client/cspNonce.d.ts +2 -0
  120. package/dist/src/dev/fileRouteRefresher.d.ts +5 -0
  121. package/dist/src/dev/importCost/instrument.d.ts +1 -1
  122. package/dist/src/dev/reactServerComponentsRebuild.d.ts +3 -0
  123. package/dist/src/i18n/defineMessages.d.ts +5 -0
  124. package/dist/src/i18n/formatMessage.d.ts +26 -0
  125. package/dist/src/i18n/index.d.ts +6 -0
  126. package/dist/src/i18n/intlCache.d.ts +5 -0
  127. package/dist/src/i18n/routing.d.ts +28 -0
  128. package/dist/src/i18n/translator.d.ts +11 -0
  129. package/dist/src/mobile/androidRelease.d.ts +2 -2
  130. package/dist/src/mobile/iosRelease.d.ts +4 -4
  131. package/dist/src/mobile/materializedBundle.d.ts +1 -1
  132. package/dist/src/mobile/mobileInspect.d.ts +1 -1
  133. package/dist/src/mobile/releaseArtifact.d.ts +2 -2
  134. package/dist/src/mobile/remoteMacProtocol.d.ts +2 -2
  135. package/dist/src/plugins/i18n.d.ts +30 -0
  136. package/dist/src/plugins/index.d.ts +2 -1
  137. package/dist/src/plugins/secureHeaders.d.ts +48 -0
  138. package/dist/src/react/components/Head.d.ts +1 -1
  139. package/dist/src/react/components/StreamSlot.browser.d.ts +1 -0
  140. package/dist/src/react/components/StreamSlot.d.ts +5 -1
  141. package/dist/src/react/components/SuspenseSlot.browser.d.ts +2 -0
  142. package/dist/src/react/components/SuspenseSlot.d.ts +12 -0
  143. package/dist/src/react/fileRoute.d.ts +18 -0
  144. package/dist/src/react/hooks/index.d.ts +4 -0
  145. package/dist/src/react/hooks/useFormAction.d.ts +42 -0
  146. package/dist/src/react/hooks/useI18n.d.ts +15 -0
  147. package/dist/src/react/index.d.ts +2 -0
  148. package/dist/src/react/server.d.ts +2 -0
  149. package/dist/src/react/serverComponents/constants.d.ts +11 -0
  150. package/dist/src/react/serverComponents/flightPayload.d.ts +2 -0
  151. package/dist/src/react/serverComponents/pageHandler.d.ts +14 -0
  152. package/dist/src/svelte/browser.d.ts +1 -0
  153. package/dist/src/svelte/fileRoute.d.ts +15 -0
  154. package/dist/src/svelte/i18n.d.ts +8 -0
  155. package/dist/src/svelte/index.d.ts +2 -0
  156. package/dist/src/svelte/lowerAwaitSlotSyntax.d.ts +5 -1
  157. package/dist/src/svelte/renderToReadableStream.d.ts +10 -2
  158. package/dist/src/svelte/router/browser.d.ts +3 -1
  159. package/dist/src/svelte/router/goto.d.ts +13 -10
  160. package/dist/src/svelte/router/guards.d.ts +43 -0
  161. package/dist/src/svelte/router/index.d.ts +3 -1
  162. package/dist/src/svelte/router/lazy.d.ts +17 -0
  163. package/dist/src/svelte/router/lazyModule.d.ts +29 -0
  164. package/dist/src/svelte/router/navigation.d.ts +55 -0
  165. package/dist/src/svelte/router/pushState.d.ts +4 -2
  166. package/dist/src/svelte/router/scroll.d.ts +34 -0
  167. package/dist/src/svelte/routerSsr.d.ts +9 -0
  168. package/dist/src/svelte/server.d.ts +1 -0
  169. package/dist/src/utils/cspNonceRef.d.ts +8 -0
  170. package/dist/src/utils/fontAssets.d.ts +17 -0
  171. package/dist/src/utils/formActionProtocol.d.ts +10 -0
  172. package/dist/src/utils/generateHeadElement.d.ts +1 -1
  173. package/dist/src/utils/index.d.ts +1 -0
  174. package/dist/src/utils/inlinePageCss.d.ts +18 -0
  175. package/dist/src/utils/loadConfig.d.ts +5 -0
  176. package/dist/src/utils/localeRef.d.ts +10 -0
  177. package/dist/src/utils/resolveConvention.d.ts +6 -1
  178. package/dist/src/utils/streamingSlots.d.ts +5 -0
  179. package/dist/src/vue/browser.d.ts +2 -0
  180. package/dist/src/vue/components/Image.d.ts +2 -2
  181. package/dist/src/vue/index.d.ts +2 -0
  182. package/dist/src/vue/serverDataPayload.d.ts +32 -0
  183. package/dist/src/vue/serverDataState.d.ts +9 -0
  184. package/dist/src/vue/useServerData.browser.d.ts +6 -0
  185. package/dist/src/vue/useServerData.d.ts +24 -0
  186. package/dist/svelte/browser.js +330 -3
  187. package/dist/svelte/browser.js.map +8 -4
  188. package/dist/svelte/components/AwaitSlot.svelte +5 -0
  189. package/dist/svelte/components/Head.svelte +50 -0
  190. package/dist/svelte/components/JsonLd.svelte +9 -1
  191. package/dist/svelte/components/StreamSlot.svelte +5 -0
  192. package/dist/svelte/index.js +1017 -117
  193. package/dist/svelte/index.js.map +38 -24
  194. package/dist/svelte/router/Link.svelte +40 -8
  195. package/dist/svelte/router/Route.svelte +157 -12
  196. package/dist/svelte/router/Route.svelte.d.ts +39 -5
  197. package/dist/svelte/router/Router.svelte +140 -25
  198. package/dist/svelte/router/Router.svelte.d.ts +5 -1
  199. package/dist/svelte/router/browser.ts +10 -0
  200. package/dist/svelte/router/goto.ts +15 -86
  201. package/dist/svelte/router/guards.ts +112 -0
  202. package/dist/svelte/router/index.ts +10 -0
  203. package/dist/svelte/router/lazy.ts +82 -0
  204. package/dist/svelte/router/lazyModule.ts +74 -0
  205. package/dist/svelte/router/navigation.ts +412 -0
  206. package/dist/svelte/router/pushState.ts +7 -4
  207. package/dist/svelte/router/scroll.ts +286 -0
  208. package/dist/svelte/server.js +747 -252
  209. package/dist/svelte/server.js.map +30 -20
  210. package/dist/types/angular.d.ts +2 -0
  211. package/dist/types/build.d.ts +37 -0
  212. package/dist/types/conventions.d.ts +3 -0
  213. package/dist/types/fileRoutes.d.ts +72 -0
  214. package/dist/types/fonts.d.ts +86 -0
  215. package/dist/types/formAction.d.ts +54 -0
  216. package/dist/types/globals.d.ts +10 -0
  217. package/dist/types/i18n.d.ts +185 -0
  218. package/dist/types/index.d.ts +4 -0
  219. package/dist/types/metadata.d.ts +11 -0
  220. package/dist/types/navigation.d.ts +37 -0
  221. package/dist/types/prefetch.d.ts +10 -0
  222. package/dist/types/sitemap.d.ts +4 -0
  223. package/dist/types/svelteRouter.d.ts +89 -2
  224. package/dist/types/svelteRouter.ts +112 -2
  225. package/dist/types/vue.d.ts +46 -1
  226. package/dist/utils/iconVersion.ts +34 -0
  227. package/dist/vue/browser.js +107 -11
  228. package/dist/vue/browser.js.map +7 -4
  229. package/dist/vue/components/index.js +5 -2
  230. package/dist/vue/components/index.js.map +5 -4
  231. package/dist/vue/index.js +712 -158
  232. package/dist/vue/index.js.map +32 -23
  233. package/dist/vue/server.js +310 -79
  234. package/dist/vue/server.js.map +20 -15
  235. package/package.json +9 -1
  236. package/dist/cli/prerender-mfnwsvyt.js +0 -7
  237. package/dist/src/plugins/pageRouter.d.ts +0 -1
  238. /package/dist/cli/{index-j7m8pq48.js → index-rervww4p.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,86 @@ This file is generated by `absolute-changelog` from the entries in
6
6
  `changelog/`. Edit an entry, not this file — and add new ones under
7
7
  `changelog/unreleased/`.
8
8
 
9
+ ## 0.20.0-beta.170 — 2026-11-13
10
+
11
+ ### Breaking
12
+
13
+ - **Two pages with the same name in the HTML and HTMX directories are a build error instead of one silently replacing the other** (`asset`, `build`)
14
+ An HTML page and an HTMX page with the same file name (both `pages/Home.html`) share the manifest key `Home`. The build only warned "Manifest key collision … overwritten", and both routes then served whichever page was processed last. The build now fails and names both source files. In dev the error is reported like any other failed build pass. Key naming is unchanged.
15
+ _Migration:_ If the build reports "Manifest key \"…\" belongs to two pages", rename one of the two files (for example `htmx/pages/Home.html` to `htmx/pages/HtmxHome.html`) and update the `asset(manifest, …)` call in its route. Builds without a collision are unaffected.
16
+ - **Svelte `{#await}` blocks render and hydrate natively; server streaming is opt-in with `<!-- absolute:stream -->`** (`handleSveltePageRequest`)
17
+ Every `{#await}` with a pending branch was rewritten into a streaming slot, and any page containing one had hydration switched off. Buttons on such a page never worked; without `collectStreamingSlots: true` the pending branch showed forever; with it, a component in the `then` branch arrived as literal text; `{#if}` inside a branch failed to compile; and a shorthand `{#await x then y}` followed by a normal block failed with "Unterminated regular expression", because the pattern matched across both. A plain `{#await}` is now left to Svelte, which renders the pending branch on the server and resolves and hydrates it in the browser. Streaming is opt-in with `<!-- absolute:stream -->` directly before the block. A streamed block's branches are sent as HTML, so a component or logic block inside one is a build error, and only pages that stream a block skip hydration.
18
+ _Migration:_ To keep server-side streaming for an `{#await}` block, put `<!-- absolute:stream -->` directly before it and keep its branches to elements and `{expressions}`. Unmarked blocks now render the pending branch on the server and resolve in the browser.
19
+
20
+ ### Removed
21
+
22
+ - **Remove the unimplemented `pageRouterPlugin` export** (`pageRouterPlugin`)
23
+ `pageRouterPlugin` was a placeholder that only logged "Page Router Plugin Not Implemented Yet" and registered nothing, so removing it changes no behavior.
24
+ _Migration:_ Delete the `pageRouterPlugin` import and call; it never did anything. Register page routes with Elysia (`.get(path, () => handle…PageRequest(…))`) as before.
25
+
26
+ ### Security
27
+
28
+ - **Initial props, request context, island state and JSON-LD can no longer break out of their inline <script>**
29
+ Vue and Ember wrote a page's initial props into an inline script with a plain `JSON.stringify`, so a prop value containing `</script>` ended the script and the rest of the value was parsed as HTML: an XSS hole for any page that passes user-provided data as props. The same applied to Angular's `requestContext`, the island state and manifest scripts, and JSON-LD (`generateHeadElement`, the React and Svelte `JsonLd` components). All of these now escape `<`, U+2028 and U+2029, as React and Svelte page props already did. The data is unchanged once parsed.
30
+ - **Production servers no longer run development code paths: error pages stop exposing stacks, and `client: 'none'` Vue pages stop shipping the dev reload client**
31
+ Bun's bundler replaces a literal `process.env.NODE_ENV` with its build-time value, and with nothing defined that value is "development". The published dist/ was built that way, so every such check was a constant: `absolute start` and compiled binaries ran development code with NODE_ENV=production. Error convention pages received `error.stack`; Vue pages with `client: 'none'` shipped the ~130 KB inline dev reload client; React pages emitted the Fast Refresh setup; Angular pages stat'ed their module on every request; and the island-bootstrap and streaming-slot dev warnings stayed on. The library build now leaves `process.env.NODE_ENV` a runtime read, the source reads it through `src/utils/runtimeMode.ts`, and a lint rule rejects a literal `process.env.NODE_ENV` in src/.
32
+ - **Production servers no longer serve their own server bundle, SSR bundles or runtime manifests as static files**
33
+ `absolute start` writes the bundled server into the build directory, and that directory is also the static root, so `GET /server.js` downloaded the whole backend. The same applied to its `.br`/`.gz` copies, to the Svelte/Vue/Angular SSR page bundles under `<framework>/server/`, to the compiled convention pages, and to `absolute-runtime.json`, `conventions.json` and `spa-routes.json`. Production static serving now excludes those, plus `.map` files and native `.node` addons. The build records every file its server pass writes in `server-outputs.json`, and those are excluded by exact path, so SSR bundles are covered wherever the layout of a project puts them (a Svelte-only build writes them to `svelte/pages/`, Angular to `angular/src/…`). The precompressed handler applies the same list. The server still reads all of them from disk.
34
+
35
+ ### Added
36
+
37
+ - **Angular pages can opt into zone.js change detection**
38
+ Angular pages ran zoneless only, so apps and libraries that rely on zone.js (NgZone, change detection after timers or manually added event listeners) didn't update. Set `angular: { changeDetection: 'zone' }` in absolute.config.ts and add zone.js to your app (`bun add zone.js`). Server rendering then waits for the timers and requests a page starts, the browser loads zone.js before Angular boots, and hydration and HMR keep working. Async functions in your Angular code are compiled to generators in this mode, because zone.js can't follow native `await`. Elysia routes keep working with zone.js loaded on the server. The default stays zoneless. Angular islands still render zoneless in zone mode.
39
+ - **Links between pages built with different frameworks can navigate without a full page reload**
40
+ Call `enableNavigation()` from `@absolutejs/absolute/client/navigation` on your pages and a link from a page in one framework to a page in another (a React page to a Svelte page, say) no longer reloads the browser. The target page's HTML is fetched, its stylesheets load first, the current page is torn down, the head and body are swapped (in a View Transition where the browser supports one), and the target page hydrates. Island stores made with `createIslandStore` keep their values, back and forward work across the two pages with their scroll positions, and the new page's title is announced to screen readers. Links inside a framework's own router still navigate through that router. Under `secureHeaders()` the swapped-in page's scripts and styles run with the current page's nonce, but only those that carried their own response's nonce. Add `data-absolute-navigation="off"` to a link (or a parent) to get a normal page load; any failure also falls back to one. The Svelte router's `<Link>` now leaves a URL that no mounted `<Router>` matches to this navigation when it is enabled. This release is tested with React and Svelte pages. Vue and Angular pages have the same teardown hook but are not tested yet. HTML and HTMX pages have none, so leaving one is still a normal page load.
41
+ - **Opt-in file routes for React and Svelte pages, with typed `load` functions** (`fileRoutes`, `reactFileRoute`, `svelteFileRoute`, `FileRouteContext`)
42
+ Opt-in file routes for React and Svelte pages. With `fileRoutes: true` in absolute.config.ts, each file under `<reactDirectory>/routes/` or `<svelteDirectory>/routes/` becomes a GET route: `index.tsx` serves `/`, `blog/index.tsx` serves `/blog`, and `blog/[slug].tsx` serves `/blog/:slug`. Files and folders starting with `_` are private, so components can live next to routes. A sibling `blog/[slug].server.ts` can export `load`, whose result becomes the page's props, and `beforeHandle`, which can answer the request first (for example with a redirect). The build writes a route module (`src/routes.gen.ts` by default, `fileRoutes.output` to change it); pass its `routeTree` to `fileRoutes(routeTree, manifest)` after `prepare()`. The route module checks types: `load` must take the route's params and return the page's props, so a renamed param or a missing prop is a type error. Explicit routes stay the default and win over a file route for the same path. In `absolute dev`, adding, renaming or removing a route file updates the routes without a restart. Catch-all, optional and group segments, Vue, Angular and HTML routes, and per-folder conventions are planned. See docs/FILE_ROUTER_PLAN.md.
43
+ - **Form actions: one handler for forms that work without JavaScript and update in place with it, on React, static HTML and HTMX pages**
44
+ `defineAction(path, { body, handler })` defines a form action: a POST route that checks the form against an Elysia `t.Object` schema and works with or without JavaScript. Without JavaScript a plain `<form method="post">` posts, the browser is sent back to the page (or to `redirect`) and the page shows the result: React and other SSR pages read it with `getActionState(action)` and pass it as a prop, and static HTML and HTMX pages get the errors, the success message and what the user typed written into `<form data-absolute-action>` by the server. With JavaScript, React's `useFormAction` hook (from `@absolutejs/absolute/react/hooks`) and `enhanceForms()` for plain pages (from `@absolutejs/absolute/client`) submit without a reload and expose pending, the typed result and one error message per field. HTMX `hx-post` forms get the action's `htmx` fragment, or behave like a plain post. Cross-site posts are refused (403), bodies over `maxBodySize` (default 10 MiB) are refused before they are read (413), and JSON clients such as Eden get JSON.
45
+ - **Layouts and partials for HTML and HTMX pages with `<abs-include>` and `<abs-slot>`**
46
+ HTML and HTMX pages can share markup with `<abs-include src="../partials/header.html"></abs-include>`, which is replaced at build time with the file's contents. Paths are relative to the including file, and included files can include others. A layout marks where content goes with `<abs-slot>` and `<abs-slot name="head">`; a page fills the slots through the include's children and `<template slot="head">`. Includes are expanded before asset paths, image optimization and island rendering, so included markup gets all three. A missing file or an include cycle fails the build and shows the chain. Only the `.html` files directly in `pages/` are pages. In dev, editing a partial or layout updates every page that includes it. See docs/HTML_INCLUDES.md.
47
+ - **Internationalization: locale routing, typed ICU message catalogs, React and Svelte translation APIs, hreflang links and a per-locale sitemap** (`defineI18n`, `i18nPlugin`, `getRequestLocale`, `I18nProvider`, `useI18n`, `setI18n`, `getI18n`, `createTranslator`, `defineMessages`, `formatMessage`, `Metadata`, `generateHeadElement`, `Head`, `SitemapConfig`, `BaseBuildConfig`)
48
+ Apps can now be translated. `defineI18n()` takes your locales, the default locale's messages and a loader for each other locale, and `i18nPlugin()` works out each request's locale from the URL prefix (`/fr/about`), the domain, or the visitor's cookie and `Accept-Language` header. Handlers get `locale` and an `i18n` object: `i18n.t('key', params)` formats ICU messages (plurals, select, numbers, dates) with the platform's `Intl`, and `i18n.payload(['namespace'])` returns the messages a page needs to hydrate, which you pass as a page prop. React pages read them with `I18nProvider` and `useI18n()` from `@absolutejs/absolute/react/hooks`; Svelte pages with `setI18n()` and `getI18n()` from `@absolutejs/absolute/svelte`. Message keys and their parameters are type-checked against the default catalog, and a locale missing a key is a type error. `i18n.alternates()` builds hreflang links, which the `alternates` field of `Metadata` renders in `generateHeadElement` and the React and Svelte `Head` components. Setting `sitemap.i18n` lists every page once per locale with hreflang alternates. Svelte pages get the request's `lang` and `dir` on `<html>`. The browser-safe helpers (`createTranslator`, `formatMessage`, `localizePath`, `negotiateLocale`, `localeDirection`) are exported from `@absolutejs/absolute/i18n`. See docs/I18N_PLAN.md.
49
+ - **Partial prerendering: pre-rendered React and Svelte pages serve a cached shell instantly and stream their `dynamic` slots per request** (`StreamSlot`, `SuspenseSlot`, `StreamingSlot`)
50
+ A pre-rendered page used to be either fully cached or fully rendered per request. Now a streaming slot marked `dynamic` (`<StreamSlot dynamic>` or `<SuspenseSlot dynamic>` in React, the `dynamic` prop or `<!-- absolute:stream dynamic -->` in Svelte) stays a hole in the pre-rendered page. On a route listed in `static.routes`, the build stores the page as a static shell: other slots are resolved into it, and dynamic slots keep their fallback. Each request gets the cached shell at once, before the route handler runs. Only the dynamic slots are then rendered, with the visitor's cookies and headers, and streamed into the page, carrying the request's CSP nonce under `secureHeaders()`. Revalidation (`static.revalidate`) re-captures the shell. Pages without a dynamic slot are pre-rendered as before. Vue and Angular support comes later.
51
+ - **Opt-in React Server Components for React pages served from Elysia routes**
52
+ Set `reactServerComponentsDirectory` in absolute.config.ts and put pages in its `pages` folder. Pages are server components: they can be async and read data directly, and that code never ships to the browser. Components marked `"use client"` are server-rendered, then hydrated in the browser. Serve a page from any Elysia route with `handleReactServerPageRequest({ page: asset(manifest, '<Page>RSC'), props })`. `<Suspense>` boundaries stream, and the inline scripts carry the CSP nonce when `secureHeaders()` is on. Install `react-server-dom-webpack` at the same version as `react` (19.2.3 or later). Existing React pages are unchanged. Server Actions, client-side navigation and HMR for these pages are not available yet; edits reload after the dev server restarts.
53
+ - **`secureHeaders()`: a strict nonce-based Content-Security-Policy and security headers for every framework** (`secureHeaders`, `getCspNonce`)
54
+ `secureHeaders()` is an opt-in Elysia plugin that sends a Content-Security-Policy with a per-request nonce (`'strict-dynamic'`, no `'unsafe-inline'` for scripts), plus `X-Content-Type-Options: nosniff`, `Referrer-Policy`, `frame-ancestors` and `Permissions-Policy`, each overridable. Every inline `<script>` and `<style>` AbsoluteJS writes carries the nonce: page bootstraps and initial props for every framework, island state, streaming-slot patches, inline page CSS, Svelte and Angular component styles, speculation rules, the error pages and the dev HMR client. The nonce is added where each script is written, so a script injected through `{@html}` / `v-html` / `dangerouslySetInnerHTML` stays blocked. Pre-rendered and ISR pages store the nonce they were rendered with and swap it for each request's, on scripts, styles and `<link rel="modulepreload">`. A page pre-rendered without the plugin is served without a policy and logged. In development the policy also allows the HMR WebSocket and `'unsafe-eval'` (Angular's dev compiler). `getCspNonce()` and the `cspNonce` context value give your own inline scripts the nonce. See docs/SECURITY_HEADERS.md.
55
+ - **Self-hosted, preloaded fonts with layout-stable fallbacks from the `fonts` config**
56
+ A new `fonts` option in `absolute.config.ts` downloads Google Fonts (or copies local font files) at build time and serves them from your own origin with content-hashed names and a one-year immutable cache. The build writes one linked stylesheet with `font-display: swap`, a size-adjusted fallback face per family so text does not shift while the font loads, and a `--font-<family>` variable and `.font-<family>` class. Pass `fontAssets(manifest)` to the `fonts` option of `Head` (React, Svelte) or `generateHeadElement`; HTML and HTMX pages get the preload and stylesheet tags automatically. Downloads are cached between builds; a failed download stops the build unless `fallbackToRemote` is set. Hashed static files served by a plain GET now also get the immutable cache header; before, only precompressed copies did.
57
+ - **Svelte router: route guards with server redirects, lazy routes, and scroll restoration** (`goto`, `beforeNavigate`, `redirect`, `preloadRoute`, `GotoOptions`, `renderToReadableStream`, `observeViewport`)
58
+ Route guards: a `guard` on `<Route>` or `<Router>`, and `beforeNavigate()` for browser-only checks, runs before `<Link>` clicks, `goto()` and back/forward, and can be async. It returns nothing to allow, `false` to cancel or a URL / `redirect()` to redirect. Cancelling back/forward puts the URL back. Route and Router guards also run during the server render, where a redirect is a 302. Lazy routes: `<Route loader={() => import("./Page.svelte")}>` puts the component in its own chunk, loaded when the route is first visited or when a `<Link>` to it is prefetched, with optional `pending` and `error` snippets. The server renders the real component and the browser loads its chunk before hydrating, so the page doesn't flash. Scroll restoration: while a router is mounted, new navigations start at the top or at the `#hash` target, and back/forward and reload return to where you were. `goto(url, { noScroll: true })` leaves the scroll alone. `goto()` now resolves to what happened (`"committed"`, `"cancelled"`, `"redirected"`, …), and `history.state` holds the router's bookkeeping next to your state, which `page.state` still returns unchanged. Smaller additions that support this: `renderToReadableStream` takes a Svelte `context`, a `settle` callback that can ask for another render pass, and a function for `bootstrapScriptContent`; `observeViewport` and `scheduleHoverPrefetch` take an `onVisible` / `onPrefetch` callback; and the `noScroll` doc on `GotoOptions` now covers `#hash` targets.
59
+ - **Vue pages can load data on the server with `useServerData` and hydrate without a second fetch**
60
+ Vue pages could only get server data as props from the route handler. `useServerData(key, fetcher)`, exported from `@absolutejs/absolute/vue`, runs the fetcher while the page renders on the server and writes the result into the page, so hydration shows it without fetching again. A client-side route change, a page that exports `routes`, or `server: false` fetches in the browser instead. It returns `data`, `error`, `pending` and `refresh` refs, with `data` typed from the fetcher. A fetcher that throws on the server renders an error state rather than failing the page (in production the message sent to the browser is generic). Using one key twice in a request fails the page with a clear error outside production. Pages with `client: 'none'` render the data but write nothing into the page, and streaming pages are supported. Streaming-slot patches also no longer land inside the page's own inline scripts.
61
+ - **Opt-in Core Web Vitals reporting with `vitals` and `onVital()`** (`onVital`)
62
+ Set `vitals` in absolute.config.ts (`{ endpoint?, sampleRate?, dev? }`) to report LCP, INP, CLS, FCP and TTFB from real visits. A production build bundles a small reporter (web-vitals plus a sender) that every framework's client bundle imports and that static HTML/HTMX pages load with a script tag. Each page load samples once, and a page that reports sends one batch, with its route and navigation type, when it's hidden or closed (`sendBeacon`, falling back to `fetch` keepalive). `prepare()` serves `POST /__absolute/vitals`, validated with a TypeBox schema. It stores nothing; register `onVital(handler)` to forward the metrics to your own store. With `vitals` unset nothing is built or injected, and dev reports only with `dev: true`. It works on pre-rendered pages and under `secureHeaders()`. See docs/WEB_VITALS.md.
63
+
64
+ ### Fixed
65
+
66
+ - **Angular HMR applies edits to components whose `styleUrl` is Less or Stylus, and reports style compile errors accurately**
67
+ The surgical Angular HMR path compiled `styleUrl` files synchronously, which Less and Stylus don't support. Editing a component whose `styleUrl` was a `.less` or `.styl` file showed "Style file not found" for a file that existed, and the update stalled behind the error overlay. The fast path now compiles Less and Stylus with their async compilers. A style that exists but fails to compile reports the compiler's error instead of "not found". The production build and the dev rebuild path already handled Less and Stylus.
68
+ - **`absolute add` edits scaffolded configs, and generated Angular pages no longer import shared files**
69
+ `absolute add <framework>` failed with "Could not find defineConfig" on the config create-absolutejs writes (`import { defineConfig } …` followed by `export default defineConfig({…})`). The config walker passed `pending.push(child)` straight to TypeScript's `forEachChild`, which stops at the first truthy callback result, so only a file's first statement (the import) was ever searched. The auth config readers behind `absolute config` had the same bug and are fixed too. `absolute generate page --framework angular` now writes a self-contained page with its nav baked in, like the static pages, instead of importing `../../shared/navData` from outside the Angular directory.
70
+ - **Production stack traces point at your server file and page components instead of the bundled output**
71
+ The production server bundle had no source map, so errors logged by `absolute start` pointed into dist/server.js. The Svelte, Vue and Angular SSR page bundles had none either; only dev chained their maps back to the source. The server bundle now gets a linked map, and the SSR page bundles get the same inline-and-chained maps as dev. A route handler error points at its line in your server file, and a page component error at its `.svelte`/`.vue` file. Svelte frames used to name a nonexistent file next to the build output (`build/svelte/pages/Page.svelte`), because Svelte records the source by basename; they now resolve to the original file, in dev too. Neither map is served publicly. The "Could not decode sourcemap" warning was fixed in the previous release.
72
+ - **Production servers start when their build directory is read-only, such as in a container running as a non-root user**
73
+ The image optimizer created its cache directory (`<build>/.cache/images` unless `images.cacheDirectory` is set) when the server started, so a production server that could not write its build directory crashed on boot with `EACCES` or `EROFS`, even if the app never optimized an image. That is the usual container setup: the build copied in as root and the server running as a non-root user, or a read-only root filesystem. The directory is now created on the first image written to the cache. When it cannot be written, images are still optimized and served, just not cached. A prepared build now starts from a read-only directory with an empty HOME and writes nothing outside TMPDIR.
74
+ - **`absolute start` no longer warns "Could not decode sourcemap in dist/server.js" on the first error**
75
+ The production server bundle has no source map, but the AbsoluteJS code inside it builds inline source map comments for Svelte and Vue output. Those were template literals starting with `\n`, which Bun's bundler emits as a real newline, leaving a line that starts `//# sourceMappingURL=data:…${…}` in the middle of dist/server.js. Bun read that line as the bundle's own source map whenever it built a stack trace, and warned that it couldn't decode it.
76
+ - **HTML and HTMX pages render their error.html when they fail, and HTMX requests get the HTMX not-found.html** (`ConventionsMap`)
77
+ `handleHTMLPageRequest` and `handleHTMXPageRequest` now render the error convention when a page can't be served (its file is missing or unreadable, or it fails a static-page check). They return status 500 with `<Page>.error.html` or `error.html`, like the framework handlers, and `{{ stack }}` is empty in production. Before, the error went unhandled. HTMX pages use the HTMX pages directory's error page first. An HTMX request (`HX-Request: true`) to an unknown route gets that directory's `not-found.html` first. Having `error.html` or `not-found.html` in both the HTML and HTMX directories no longer counts as a page name collision. `loading.html` doesn't apply to static pages; see docs/STATIC_PAGE_CONVENTIONS.md.
78
+ - **React `SuspenseSlot` content streams in production builds** (`SuspenseSlot`)
79
+ `SuspenseSlot` rendered its resolved content with React's `renderToStaticMarkup`, but `absolute start` strips React's legacy string renderer out of the production server bundle to save about 4,000 lines. In production every SuspenseSlot patch failed with `renderToStaticMarkup is not a function`, so the fallback stayed on the page. The slot now renders with `renderToReadableStream`, which the bundle keeps.
80
+ - **Apps using the Svelte `Head` component build again**
81
+ The packaged Svelte `Head` component imports a small icon helper by relative path, and the package shipped the component without that file, so any app using `Head` from `@absolutejs/absolute/svelte/components/Head.js` failed to build with "Could not resolve ../../utils/iconVersion". The helper now ships next to the components.
82
+ - **Svelte HMR keeps component state instead of remounting the page with a number read from the first button**
83
+ On every Svelte component update the HMR client read the first number out of the page's first `<button>`, stored it in sessionStorage and remounted the page with it as an `initialCount` prop. On a page whose first button said "Page 1", editing any component reset a counter elsewhere on the page and injected `initialCount: 1`. The scaffold's demo only worked because its first button happened to be the counter. The client now leaves state to Svelte's own HMR and never reads it from the DOM. The dev server also stopped re-importing the components that import an edited `.svelte` file: `$.hmr` already swaps the edited component in place, and re-importing the page re-created every component under it. As in any Svelte setup, the edited component itself restarts from its props, and components elsewhere on the page keep their `$state`.
84
+ - **Svelte pages that use `getContext` / `setContext`, including every Svelte router page, no longer fail under `absolute start`**
85
+ `absolute start` bundled its own copy of Svelte into the production server, while Svelte pages load Svelte from node_modules. The two copies kept separate component context, so a page calling `getContext` or `setContext` (the Svelte router does) answered every request with a 500 `lifecycle_outside_component`. The production server now keeps Svelte external when the app has Svelte pages, so there is one copy.
86
+ - **Vue pages with `client: 'none'` set through a same-file const skip their unused client bundle**
87
+ The build drops the client bundle of a Vue page registered with `client: 'none'`, but it only recognised a string literal. `client: CLIENT_MODE` with `const CLIENT_MODE = 'none'` in the same file still built an unused hydration bundle. The scanner now resolves same-file `const` strings for `client` and for the `asset(manifest, …)` key, including `as const`, `satisfies` and the shorthand `{ client }`. A value it can't know at build time (an import, a `let`, a call) keeps the client bundle, which still works and just isn't trimmed.
88
+
9
89
  ## 0.20.0-beta.169 — 2026-11-12
10
90
 
11
91
  ### Fixed
package/README.md CHANGED
@@ -122,6 +122,188 @@ same errors in your editor, add the declarations to `rootDirs`:
122
122
  }
123
123
  ```
124
124
 
125
+ ### Svelte `{#await}` and streaming
126
+
127
+ A plain `{#await}` works as it does in Svelte: the server renders the pending
128
+ branch, then the browser resolves the promise and hydrates, so components,
129
+ `{#if}` and event handlers work in every branch.
130
+
131
+ To have the server resolve the promise and stream the result into the page
132
+ instead, put `<!-- absolute:stream -->` directly before the block and render
133
+ the page with `collectStreamingSlots: true`:
134
+
135
+ ```svelte
136
+ <!-- absolute:stream -->
137
+ {#await loadPrices()}
138
+ <p>Loading prices…</p>
139
+ {:then prices}
140
+ <p>{prices.length} prices, from {prices[0].source}</p>
141
+ {:catch error}
142
+ <p>Prices are unavailable: {error.message}</p>
143
+ {/await}
144
+ ```
145
+
146
+ A streamed block's branches are sent as HTML, so they may contain elements and
147
+ `{expressions}` only. A component, `{#if}`, `{#each}` or `{@html}` in a branch
148
+ is a build error. A page that streams a block isn't hydrated, so use it for
149
+ content that needs no interactivity.
150
+
151
+ ### Partial prerendering
152
+
153
+ On a route listed in `static.routes`, mark the streaming slots that depend on
154
+ the request as `dynamic`. The build pre-renders the rest of the page as a
155
+ static shell, with every other slot resolved into it. Each request then gets
156
+ the cached shell immediately, even before the route handler runs, and only
157
+ the dynamic slots are rendered and streamed into it:
158
+
159
+ ```tsx
160
+ <StreamSlot
161
+ dynamic
162
+ id="greeting"
163
+ fallbackHtml="<p>…</p>"
164
+ resolve={() => greet(user)}
165
+ />
166
+ ```
167
+
168
+ In Svelte, use the `dynamic` prop or `<!-- absolute:stream dynamic -->`. The
169
+ page needs `collectStreamingSlots: true`. The shell's page props are captured
170
+ at build time, so per-user data belongs in dynamic slots. Works for React
171
+ and Svelte pages today. See `docs/PARTIAL_PRERENDERING_PLAN.md`.
172
+
173
+ ### Svelte router: guards, lazy routes and scroll restoration
174
+
175
+ `<Router>`, `<Route>` and `<Link>` from `@absolutejs/absolute/svelte/router/*.svelte`
176
+ route inside one Svelte page. Routes can be guarded and lazily loaded, and
177
+ the router manages the scroll position:
178
+
179
+ ```svelte
180
+ <script lang="ts">
181
+ import Router from '@absolutejs/absolute/svelte/router/Router.svelte';
182
+ import Route from '@absolutejs/absolute/svelte/router/Route.svelte';
183
+ import Link from '@absolutejs/absolute/svelte/router/Link.svelte';
184
+ import {
185
+ beforeNavigate,
186
+ redirect
187
+ } from '@absolutejs/absolute/svelte/router';
188
+
189
+ let { url }: { url?: string } = $props();
190
+
191
+ // Browser only: runs before every Link click, goto() and back/forward.
192
+ beforeNavigate(({ type }) => {
193
+ if (type === 'popstate' && hasUnsavedChanges()) return false;
194
+ });
195
+ </script>
196
+
197
+ <Router {url}>
198
+ <Link to="/app/editor/1">Editor</Link>
199
+
200
+ <Route
201
+ path="/app/admin"
202
+ guard={({ request }) => {
203
+ // Runs in the browser and during the server render.
204
+ if (!isAdmin(request)) throw redirect('/app/login');
205
+ }}
206
+ >
207
+ {#snippet content()}<AdminPanel />{/snippet}
208
+ </Route>
209
+
210
+ <Route path="/app/editor/:id" loader={() => import('./Editor.svelte')}>
211
+ {#snippet pending()}<p>Loading the editor…</p>{/snippet}
212
+ {#snippet error(reason, retry)}<button onclick={retry}>Retry</button
213
+ >{/snippet}
214
+ </Route>
215
+ </Router>
216
+ ```
217
+
218
+ - **Guards** (`guard` on a `<Route>` or `<Router>`, plus `beforeNavigate`) can
219
+ be async and return nothing to allow, `false` to cancel, or a URL /
220
+ `redirect()` to redirect. They run for `<Link>` clicks, `goto()` and
221
+ back/forward; cancelling a back/forward puts the URL back. Route and Router
222
+ guards also run during the server render, where a redirect is a real 302
223
+ (and a cancel a 403); they get the request as `request`.
224
+ - **Lazy routes** (`loader`) are split into their own chunk, loaded when the
225
+ route is first needed or when a `<Link>` to it is prefetched (hover /
226
+ viewport). The server awaits the loader, so the HTML holds the real
227
+ component, and the browser loads the chunk before hydrating, so there is no
228
+ pending flash. `pending` shows while a client-side navigation waits for the
229
+ chunk. The component gets the route params (and `props`) as props.
230
+ - **Scroll**: while a router is mounted it sets
231
+ `history.scrollRestoration = 'manual'`, starts new navigations at the top
232
+ (or at the `#hash` target), and restores the position on back/forward and
233
+ reload. `goto(url, { noScroll: true })` or `<Link noScroll>` leaves the
234
+ scroll alone.
235
+
236
+ ### Vue server data
237
+
238
+ `useServerData(key, fetcher)` loads data while a Vue page renders on the
239
+ server and sends it with the page, so hydration reuses it instead of fetching
240
+ again. A client-side route change, or a page whose data wasn't loaded on the
241
+ server, fetches in the browser. `data` is typed from the fetcher:
242
+
243
+ ```vue
244
+ <script setup lang="ts">
245
+ import { useServerData } from '@absolutejs/absolute/vue';
246
+
247
+ const { data, error, pending, refresh } = useServerData('posts', loadPosts);
248
+ </script>
249
+ ```
250
+
251
+ Keys must be unique within a request, and results must be JSON-serializable. A
252
+ fetcher that throws on the server renders an error state instead of failing
253
+ the page. See [docs/VUE_SERVER_DATA.md](docs/VUE_SERVER_DATA.md).
254
+
255
+ ### File routes (opt-in)
256
+
257
+ Routes are explicit Elysia calls by default. For React and Svelte pages you
258
+ can opt into file routes instead: with `fileRoutes: true` in
259
+ `absolute.config.ts`, every file under `<reactDirectory>/routes/` or
260
+ `<svelteDirectory>/routes/` is a GET route.
261
+
262
+ ```
263
+ react/routes/index.tsx → /
264
+ react/routes/blog/index.tsx → /blog
265
+ react/routes/blog/[slug].tsx → /blog/:slug
266
+ react/routes/blog/[slug].server.ts (load + beforeHandle for that route)
267
+ react/routes/_components/Card.tsx (private: `_` files are not routes)
268
+ ```
269
+
270
+ ```ts
271
+ // react/routes/blog/[slug].server.ts
272
+ import type { FileRouteContext } from '@absolutejs/absolute';
273
+
274
+ export const load = ({ params }: FileRouteContext<'/blog/:slug'>) => ({
275
+ post: getPost(params.slug)
276
+ });
277
+ ```
278
+
279
+ The build writes `src/routes.gen.ts` (`fileRoutes.output` to move it); serve
280
+ it with `.use(fileRoutes(routeTree, manifest))`. It type-checks each `load`
281
+ against its route's params and its page's props. Explicit routes win over a
282
+ file route for the same path, and `absolute dev` picks up added or removed
283
+ route files without a restart. See
284
+ [docs/FILE_ROUTER_PLAN.md](docs/FILE_ROUTER_PLAN.md).
285
+
286
+ ### Layouts and partials for HTML pages
287
+
288
+ HTML and HTMX pages can share headers, footers and layouts with build-time
289
+ includes:
290
+
291
+ ```html
292
+ <!-- html/pages/About.html -->
293
+ <abs-include src="../layouts/base.html">
294
+ <template slot="head"><title>About us</title></template>
295
+ <h1>About us</h1>
296
+ <abs-include src="../partials/team.html"></abs-include>
297
+ </abs-include>
298
+ ```
299
+
300
+ The layout puts the content where its `<abs-slot>` and
301
+ `<abs-slot name="head">` tags are. Only files directly in `pages/` are
302
+ pages, so keep partials and layouts in folders like `html/partials/`.
303
+ Islands, asset links and images inside included markup are handled like a
304
+ page's own, and in dev, editing a partial updates every page that uses it.
305
+ See [docs/HTML_INCLUDES.md](docs/HTML_INCLUDES.md).
306
+
125
307
  ### Islands on HTML and HTMX pages
126
308
 
127
309
  Static HTML and HTMX pages can embed components of any framework as islands.
@@ -186,6 +368,38 @@ startIslands({
186
368
  If a page has islands but the build produced no bootstrap, the dev server logs
187
369
  a warning instead of serving islands that never hydrate.
188
370
 
371
+ ### Security headers and CSP
372
+
373
+ `secureHeaders()` adds a strict Content-Security-Policy with a fresh nonce
374
+ per request, plus `X-Content-Type-Options`, `Referrer-Policy`,
375
+ `frame-ancestors` and `Permissions-Policy`:
376
+
377
+ ```ts
378
+ export const server = new Elysia()
379
+ .use(secureHeaders())
380
+ .use(absolutejs);
381
+ ```
382
+
383
+ Scripts run only with the nonce (`'strict-dynamic'`, no `'unsafe-inline'`),
384
+ and every inline script and style AbsoluteJS writes carries it, for every
385
+ framework, including streaming slots, islands, pre-rendered pages and the dev
386
+ HMR client. Use `getCspNonce()` for your own inline scripts. See
387
+ [docs/SECURITY_HEADERS.md](docs/SECURITY_HEADERS.md).
388
+
389
+ ### Web vitals
390
+
391
+ Turn on `vitals` to report LCP, INP, CLS, FCP and TTFB from real visits:
392
+
393
+ ```ts
394
+ // absolute.config.ts
395
+ vitals: { sampleRate: 0.25 }
396
+ ```
397
+
398
+ Every page then loads a small reporter that sends one batch per page, with
399
+ its route, to `POST /__absolute/vitals`. Nothing is stored by default; receive
400
+ the metrics with `onVital((report) => …)`. Off by default, and off in dev
401
+ unless `dev: true`. See [docs/WEB_VITALS.md](docs/WEB_VITALS.md).
402
+
189
403
  ### Installable PWA and offline Sync
190
404
 
191
405
  Enable the web-app shell once in `absolutejs.config.ts`. AbsoluteJS generates a
@@ -408,6 +622,24 @@ errors when unavailable. Haptics uses vibration where supported and otherwise
408
622
  safely becomes a no-op, because tactile feedback must never be required to
409
623
  complete an action.
410
624
 
625
+ ### Self-hosted fonts
626
+
627
+ List your fonts in `absolute.config.ts` and the build downloads them from
628
+ Google Fonts (or copies local files), serves them from your own origin with
629
+ hashed names, preloads the critical faces and adds a metric-matched fallback
630
+ so text doesn't shift while the font loads:
631
+
632
+ ```ts
633
+ export default defineConfig({
634
+ fonts: { families: [{ family: 'Poppins', weights: [400, 700] }] }
635
+ });
636
+ ```
637
+
638
+ Pass `fontAssets(manifest)` as the `fonts` option of `Head` or
639
+ `generateHeadElement`; HTML and HTMX pages get the tags automatically. Each
640
+ family gets a `--font-poppins` variable and a `.font-poppins` class. See
641
+ [docs/FONTS.md](docs/FONTS.md).
642
+
411
643
  ### Immutable production images
412
644
 
413
645
  Build production assets and the server bundle while constructing the image,