From c47329ec698a2f71421f29b4ed9353e33b2467b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 19:59:27 +0100 Subject: [PATCH 01/59] docs: remove TODO placeholders and fill in missing content Fill in documentation for `.route()`, `.onError()` route, and `.notFound()` route examples in concepts/app.md. Replace TODO in plugins/index.md with a list of built-in plugins. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/app.md | 29 +++++++++++++++++++++++++---- docs/latest/plugins/index.md | 9 ++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/docs/latest/concepts/app.md b/docs/latest/concepts/app.md index 5c9c405e661..51b44993279 100644 --- a/docs/latest/concepts/app.md +++ b/docs/latest/concepts/app.md @@ -257,7 +257,18 @@ app.fsRoutes("/foo/bar"); ## `.route()` -TODO +Register a route with a component and optional handlers for data loading. + +```tsx +app.route("/about", { + component: (ctx) =>

About {ctx.data.name}

, + handlers: { + GET(ctx) { + return page({ name: "Fresh" }); + }, + }, +}); +``` ## `.appWrapper()` @@ -282,9 +293,13 @@ app.onError("*", (ctx) => { }); ``` -Setting a route: +Setting a route with a component: -TODO +```tsx +app.onError((ctx) => { + return ctx.render(

Oops! Something went wrong.

); +}); +``` ## `.notFound()` @@ -296,7 +311,13 @@ app.notFound(() => { }); ``` -TODO: Route +With a component: + +```tsx +app.notFound((ctx) => { + return ctx.render(

Page not found

); +}); +``` ## `.mountApp()` diff --git a/docs/latest/plugins/index.md b/docs/latest/plugins/index.md index 02e0b9218a8..abdec2ff4f8 100644 --- a/docs/latest/plugins/index.md +++ b/docs/latest/plugins/index.md @@ -22,4 +22,11 @@ const addXFreshHeader = define.middleware(async (ctx) => { Learn more about [middlewares](/docs/concepts/middleware). -TODO: Show more ways +## Built-in plugins + +Fresh ships with the following plugins: + +- [cors()](/docs/plugins/cors) - Set CORS HTTP headers +- [csrf()](/docs/plugins/csrf) - CSRF protection +- [csp()](/docs/plugins/csp) - Content Security Policy headers +- [trailingSlashes()](/docs/plugins/trailing-slashes) - Enforce trailing slash behavior From daead8bde78e1077f5a547207f6d737c2c2f7aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 19:59:41 +0100 Subject: [PATCH 02/59] docs: fix broken code examples - Fix syntax error in concepts/layouts.md (missing parens around arrow fn params) - Add missing `ctx` parameter in concepts/routing.md blog handler - Fix wrong import `app` -> `App` in plugins/csrf.md - Remove unused `target` prop from Countdown example in getting-started Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/layouts.md | 2 +- docs/latest/concepts/routing.md | 2 +- docs/latest/getting-started/index.md | 2 +- docs/latest/plugins/csrf.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/latest/concepts/layouts.md b/docs/latest/concepts/layouts.md index 53110a10ea7..f9dbe8bdb59 100644 --- a/docs/latest/concepts/layouts.md +++ b/docs/latest/concepts/layouts.md @@ -28,7 +28,7 @@ set by middleware is available via `props.state`. ```tsx routes/sub/_layout.tsx import { define } from "../../utils.ts"; -export default define.layout({ Component, state }) => { +export default define.layout(({ Component, state }) => { // do something with state here return (
diff --git a/docs/latest/concepts/routing.md b/docs/latest/concepts/routing.md index da5f1850114..920dad57dd9 100644 --- a/docs/latest/concepts/routing.md +++ b/docs/latest/concepts/routing.md @@ -16,7 +16,7 @@ const app = new App() const id = ctx.params.id; return new Response(`Book id: ${id}`); }) - .get("/blog/:post/comments", () => { + .get("/blog/:post/comments", (ctx) => { // Responds to: GET /blog/my-post/comments, /blog/hello/comments, etc const post = ctx.params.post; return new Response(`Blog post comments for post: ${post}`); diff --git a/docs/latest/getting-started/index.md b/docs/latest/getting-started/index.md index 9b85c9c471f..5c03981a944 100644 --- a/docs/latest/getting-started/index.md +++ b/docs/latest/getting-started/index.md @@ -113,7 +113,7 @@ Create a new file at `islands/Countdown.tsx` import { useSignal } from "@preact/signals"; import { useEffect } from "preact/hooks"; -export function Countdown(props: { target: string }) { +export function Countdown() { const count = useSignal(10); useEffect(() => { diff --git a/docs/latest/plugins/csrf.md b/docs/latest/plugins/csrf.md index 32613721f25..f89fe7b0e0f 100644 --- a/docs/latest/plugins/csrf.md +++ b/docs/latest/plugins/csrf.md @@ -14,7 +14,7 @@ header. to HTTP requests. These allow the server to indicate which origins from. ```ts main.ts -import { app, csrf } from "fresh"; +import { App, csrf } from "fresh"; const app = new App(); From f1fe5faadbcdd2f97164b106a60615a22f2198cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 19:59:56 +0100 Subject: [PATCH 03/59] docs: fix copy-pasted frontmatter descriptions - concepts/app.md: was "Add a global app wrapper..." (from app-wrapper page) - concepts/context.md: was "Plugins can add..." (from plugins page) - advanced/environment-variables.md: was "Error pages can be used..." (from error-handling page) Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/environment-variables.md | 2 +- docs/latest/concepts/app.md | 2 +- docs/latest/concepts/context.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/latest/advanced/environment-variables.md b/docs/latest/advanced/environment-variables.md index 2d46eeaeaac..463fbf39aff 100644 --- a/docs/latest/advanced/environment-variables.md +++ b/docs/latest/advanced/environment-variables.md @@ -1,6 +1,6 @@ --- description: | - Error pages can be used to customize the page that is shown when an error occurs in the application. + How to use environment variables in Fresh, including public variables that are inlined into island bundles. --- Environment variables in Deno are typically read via `Deno.env.get()` or diff --git a/docs/latest/concepts/app.md b/docs/latest/concepts/app.md index 51b44993279..d908313819f 100644 --- a/docs/latest/concepts/app.md +++ b/docs/latest/concepts/app.md @@ -1,6 +1,6 @@ --- description: | - Add a global app wrapper to provide common meta tags or context for application routes. + The App class is the heart of Fresh, used to define routes, middlewares, layouts and more. --- The `App` class is the heart of Fresh and routes incoming requests to the diff --git a/docs/latest/concepts/context.md b/docs/latest/concepts/context.md index 473966611d4..a5c75636455 100644 --- a/docs/latest/concepts/context.md +++ b/docs/latest/concepts/context.md @@ -1,5 +1,5 @@ --- -description: Plugins can add new functionality to Fresh without requiring significant complexity. +description: The Context object is shared across all middlewares and provides access to the request, URL, params, state, and response helpers. --- The `Context` instance is shared across all middlewares in Fresh. Use it to From 1b226394b6d48aae7d9fecd2f0870f392c064323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:00:09 +0100 Subject: [PATCH 04/59] docs: fix CSRF page using CORS description The CSRF plugin page had copy-pasted text describing CORS behavior ("which origins...is permitted to load resources from"). Replaced with an accurate description of CSRF protection mechanics. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/plugins/csrf.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/latest/plugins/csrf.md b/docs/latest/plugins/csrf.md index f89fe7b0e0f..f4a6e522bba 100644 --- a/docs/latest/plugins/csrf.md +++ b/docs/latest/plugins/csrf.md @@ -2,16 +2,14 @@ description: "Prevent Cross-Site Request Forgery with this middleware" --- -The `csrf()` middleware can be used to add safeguard against +The `csrf()` middleware can be used to safeguard against [Cross-Site Request Forgery vulnerabilities](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF). -It checks if the user is allowed to load the requested URL based on the values -in the +It verifies that state-changing requests (POST, PUT, DELETE, etc.) originate +from your own site by checking the [`Sec-Fetch-Site`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site) -header and +and [`Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin) -header. to HTTP requests. These allow the server to indicate which origins -(domains, scheme or port) other than its own is permitted to load resources -from. +headers. Requests from untrusted origins are rejected. ```ts main.ts import { App, csrf } from "fresh"; From 1fbed752b99144045327c224d2eceef5d9bf8444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:01:07 +0100 Subject: [PATCH 05/59] docs: fix grammar errors and typos across documentation - "it's subfolders" -> "its subfolders" (middleware.md) - "other's like" -> "others like" (define.md) - "than this is likely" -> "then this is likely" (troubleshooting.md) - "as well has collect" -> "as well as collect" (deno-deploy.md) - "the the correct" -> "the correct" (cloudflare-workers.md) - "host name number" -> "host name" (deno-compile.md) - "While this generally desired" -> "While this is generally desired" (rendering-raw-html.md) - "If can't update" -> "If you can't update" (builder.md) - Missing closing quotes in code comments (context.md, app.md) - Image filename typo "cotuntdown" -> "countdown" (getting-started) - "_layout_.tsx" -> "_layout.tsx" (layouts.md) Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/builder.md | 2 +- docs/latest/advanced/define.md | 2 +- docs/latest/advanced/troubleshooting.md | 2 +- docs/latest/concepts/app.md | 2 +- docs/latest/concepts/context.md | 2 +- docs/latest/concepts/layouts.md | 2 +- docs/latest/concepts/middleware.md | 2 +- docs/latest/deployment/cloudflare-workers.md | 2 +- docs/latest/deployment/deno-compile.md | 2 +- docs/latest/deployment/deno-deploy.md | 2 +- docs/latest/examples/rendering-raw-html.md | 2 +- docs/latest/getting-started/index.md | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/latest/advanced/builder.md b/docs/latest/advanced/builder.md index a4c4d5d7864..06cec0e3ac4 100644 --- a/docs/latest/advanced/builder.md +++ b/docs/latest/advanced/builder.md @@ -183,7 +183,7 @@ tailwind(builder, app, { ### Tailwindcss v3 -If can't update to the current version of tailwindcss we have a dedicated +If you can't update to the current version of tailwindcss we have a dedicated `@fresh/plugin-tailwindcss-v3` plugin that uses tailwindcss v3. That way you can decided on your own when it's best to update to v4. diff --git a/docs/latest/advanced/define.md b/docs/latest/advanced/define.md index 2015ebfa9cc..73a9d2eefa2 100644 --- a/docs/latest/advanced/define.md +++ b/docs/latest/advanced/define.md @@ -5,7 +5,7 @@ description: | Define helpers can be used to shorten the amount of types you have to type yourself in code. They are entirely optional as some developers prefer the -explicitness of types, other's like the convenience of `define.*` helpers. +explicitness of types, others like the convenience of `define.*` helpers. Without define helpers: diff --git a/docs/latest/advanced/troubleshooting.md b/docs/latest/advanced/troubleshooting.md index 93972cee73d..56058b316c0 100644 --- a/docs/latest/advanced/troubleshooting.md +++ b/docs/latest/advanced/troubleshooting.md @@ -55,7 +55,7 @@ anymore and you should use the relevant npm package directly from npm. > [info]: Not using `esm.sh` solves many issues and footguns with duplicate > Preact versions in your app. If you're seeing strange JavaScript errors in the -> browser in your app than this is likely the cause. +> browser in your app then this is likely the cause. ## Attach a debugger diff --git a/docs/latest/concepts/app.md b/docs/latest/concepts/app.md index d908313819f..e36ad5af8ae 100644 --- a/docs/latest/concepts/app.md +++ b/docs/latest/concepts/app.md @@ -28,7 +28,7 @@ const app = new App() }) .get("/", () => new Response("hello")) .use((ctx) => { - // Will only be called for `/about + // Will only be called for `/about` return ctx.next(); }) .get("/about", (ctx) => ctx.render(

About me

)); diff --git a/docs/latest/concepts/context.md b/docs/latest/concepts/context.md index a5c75636455..726d08e78f4 100644 --- a/docs/latest/concepts/context.md +++ b/docs/latest/concepts/context.md @@ -56,7 +56,7 @@ matched. ```ts app.get("/foo/:id", (ctx) => { - console.log(ctx.route); // Logs: "/foo/:id + console.log(ctx.route); // Logs: "/foo/:id" // ... }); ``` diff --git a/docs/latest/concepts/layouts.md b/docs/latest/concepts/layouts.md index f9dbe8bdb59..cfb598eb128 100644 --- a/docs/latest/concepts/layouts.md +++ b/docs/latest/concepts/layouts.md @@ -68,7 +68,7 @@ structure like this: ```txt-files Project structure └── /routes    ├── sub -    │ ├── _layout_.tsx +    │ ├── _layout.tsx    │ ├── special.tsx # should not inherit layouts    │   └── index.tsx └── _layout.tsx diff --git a/docs/latest/concepts/middleware.md b/docs/latest/concepts/middleware.md index 3322f98d713..15535a8f7b1 100644 --- a/docs/latest/concepts/middleware.md +++ b/docs/latest/concepts/middleware.md @@ -58,7 +58,7 @@ Fresh ships with the following middlewares built-in: ## Filesystem-based middlewares With file system based routing you can define a middleware in a `_middleware.ts` -file inside the `routes/` folder or any of it's subfolders. +file inside the `routes/` folder or any of its subfolders. ```ts routes/_middleware.ts import { define } from "../utils.ts"; diff --git a/docs/latest/deployment/cloudflare-workers.md b/docs/latest/deployment/cloudflare-workers.md index 9e70fe23a38..6fb901328f2 100644 --- a/docs/latest/deployment/cloudflare-workers.md +++ b/docs/latest/deployment/cloudflare-workers.md @@ -36,5 +36,5 @@ Check out the [Cloudflare Documentation](https://developers.cloudflare.com/workers/vite-plugin/) for further information. -> [info]: Make sure that you set the the correct entrypoint in your +> [info]: Make sure that you set the correct entrypoint in your > `wrangler.jsonc` file. It should point to `"main": "./server.js"` diff --git a/docs/latest/deployment/deno-compile.md b/docs/latest/deployment/deno-compile.md index 45661e9fd00..5b116c7f171 100644 --- a/docs/latest/deployment/deno-compile.md +++ b/docs/latest/deployment/deno-compile.md @@ -17,4 +17,4 @@ deno compile --output my-app --include _fresh -A _fresh/compiled-entry.js The compiled entry supports two environment variables out of the box: - `PORT` to set the port number (`PORT=4000 my-app`) -- `HOSTNAME` to set the host name number (`HOSTNAME=0.0.0.0 my-app`) +- `HOSTNAME` to set the host name (`HOSTNAME=0.0.0.0 my-app`) diff --git a/docs/latest/deployment/deno-deploy.md b/docs/latest/deployment/deno-deploy.md index dce3d4e0468..e8b3881a2f7 100644 --- a/docs/latest/deployment/deno-deploy.md +++ b/docs/latest/deployment/deno-deploy.md @@ -4,7 +4,7 @@ description: "Deploy Fresh on Deno Deploy" The recommended way to deploy Fresh is by using [Deno Deploy](https://deno.com/deploy). It will automatically create branch -previews for pull requests, collect request and HTTP metrics, as well has +previews for pull requests, collect request and HTTP metrics, as well as collect traces for you out of the box. 1. Log in to [Deno Deploy](https://deno.com/deploy) diff --git a/docs/latest/examples/rendering-raw-html.md b/docs/latest/examples/rendering-raw-html.md index e4ab9643b35..fbd107bc1f0 100644 --- a/docs/latest/examples/rendering-raw-html.md +++ b/docs/latest/examples/rendering-raw-html.md @@ -4,7 +4,7 @@ description: | --- Text content in Fresh is always escaped, whether serverside rendered or rendered -in islands. While this generally desired, it can create issues in certain +in islands. While this is generally desired, it can create issues in certain situations. To address this you can render raw HTML via Preact's `dangerouslySetInnerHTML` diff --git a/docs/latest/getting-started/index.md b/docs/latest/getting-started/index.md index 5c03981a944..db7465a2fc5 100644 --- a/docs/latest/getting-started/index.md +++ b/docs/latest/getting-started/index.md @@ -155,4 +155,4 @@ export default define.page(() => { Now, we can see our countdown in action: -![Screenshot of the countdown component](/docs/getting-started-3-cotuntdown.png) +![Screenshot of the countdown component](/docs/getting-started-3-countdown.png) From 066622cc6710867fdeda09c33b5a24a563f481fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:01:21 +0100 Subject: [PATCH 06/59] docs: add navigation links to advanced index page The advanced section index was a single sentence with no links to its sub-pages. Added a complete list of all advanced topics with brief descriptions. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/index.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/latest/advanced/index.md b/docs/latest/advanced/index.md index f94ceab7521..5ae2b1aae6a 100644 --- a/docs/latest/advanced/index.md +++ b/docs/latest/advanced/index.md @@ -4,3 +4,15 @@ description: | --- This section of the documentation describes advanced functionality of Fresh. + +- [App wrapper](/docs/advanced/app-wrapper) - Customize the outer HTML structure +- [Layouts](/docs/advanced/layouts) - Define programmatic layouts +- [Error handling](/docs/advanced/error-handling) - Custom error and 404 pages +- [Partials](/docs/advanced/partials) - Client-side partial page updates +- [Forms](/docs/advanced/forms) - Handle form submissions +- [Define helpers](/docs/advanced/define) - Type-safe route and middleware helpers +- [Environment variables](/docs/advanced/environment-variables) - Use env vars in islands +- [Modifying <head>](/docs/advanced/head) - Manage meta tags, titles, and styles +- [Vite plugin options](/docs/advanced/vite) - Configure the Fresh Vite plugin +- [Troubleshooting](/docs/advanced/troubleshooting) - Common issues and solutions +- [Builder (Legacy)](/docs/advanced/builder) - Pre-Vite build system From d08b2935c03829c2f2b6603589996e78e0491e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:06:25 +0100 Subject: [PATCH 07/59] docs: expand routing page with priority, methods, and file handlers The routing page was ~100 words with only basic examples (one broken). Now covers route matching priority (static-first, then registration order), HTTP method handlers, HEAD fallback behavior, and file-based route handler patterns. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/routing.md | 70 ++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/docs/latest/concepts/routing.md b/docs/latest/concepts/routing.md index 920dad57dd9..d232a8b6812 100644 --- a/docs/latest/concepts/routing.md +++ b/docs/latest/concepts/routing.md @@ -1,6 +1,6 @@ --- description: | - File based routing is the simplest way to do routing in Fresh apps. Additionally custom patterns can be configured per route. + How routing works in Fresh, including route patterns, matching priority, method-specific handlers, and URLPattern support. --- Routing defines which middlewares and routes should respond to a particular @@ -30,3 +30,71 @@ const app = new App() Fresh supports the full [`URLPattern`](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API) syntax for setting pathnames. + +## Route matching priority + +Routes are matched in the following order: + +1. **Static routes** (exact path match like `/about`) are checked first and + always take precedence. +2. **Dynamic routes** (patterns like `/posts/:id`) are checked in the order they + were registered. The first matching route wins. + +This means the registration order matters for dynamic routes: + +```ts main.ts +const app = new App() + // This is checked first since it's registered first + .get("/posts/featured", () => new Response("Featured posts")) + // This is checked second — won't match "/posts/featured" because it's + // already handled above + .get("/posts/:id", (ctx) => new Response(`Post: ${ctx.params.id}`)); +``` + +## HTTP method handlers + +Fresh provides method-specific route registration via `.get()`, `.post()`, +`.put()`, `.delete()`, `.head()`, `.patch()`, and `.options()`. Each method +only responds to its matching HTTP verb. + +Use `.all()` to respond to any HTTP method: + +```ts main.ts +app.all("/api/health", () => new Response("ok")); +``` + +If a route is registered for `GET` but receives a `POST` request, Fresh returns +a `405 Method Not Allowed` response. `HEAD` requests automatically fall back to +the `GET` handler if no dedicated `HEAD` handler is defined. + +## File-based route handlers + +In file-based routes, export a `handlers` object with method-specific functions: + +```ts routes/api/users.ts +import { define } from "@/utils.ts"; + +export const handlers = define.handlers({ + GET(ctx) { + return new Response(JSON.stringify({ users: [] }), { + headers: { "Content-Type": "application/json" }, + }); + }, + POST(ctx) { + return new Response("Created", { status: 201 }); + }, +}); +``` + +To handle all methods, export a single function instead: + +```ts routes/api/health.ts +import { define } from "@/utils.ts"; + +export const handlers = define.handlers((ctx) => { + return new Response(`Received a ${ctx.req.method} request`); +}); +``` + +See [File routing](/docs/concepts/file-routing) for more on the file-based +routing convention. From 12931a1ac67674674fa53319461727fa7fc145fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:06:49 +0100 Subject: [PATCH 08/59] docs: expand Vite plugin page with HMR, plugin composition, and internals The Vite page was ~100 words showing only the config options. Now explains what the plugin does (JSX, aliasing, HMR, code splitting), how to add other Vite plugins alongside Fresh, how HMR works, and debugging tips. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/vite.md | 79 ++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/docs/latest/advanced/vite.md b/docs/latest/advanced/vite.md index 8cf003129a8..e3abd987cc5 100644 --- a/docs/latest/advanced/vite.md +++ b/docs/latest/advanced/vite.md @@ -1,12 +1,18 @@ --- description: | - Configure the fresh vite plugin. + Configure the Fresh Vite plugin, add other Vite plugins, and understand how Fresh integrates with Vite. --- -The Fresh vite plugin can be optionally configured in the following ways: +Fresh 2 uses [Vite](https://vite.dev/) for development and production builds. +The Fresh Vite plugin handles JSX configuration, Hot Module Replacement (HMR), +island discovery, client/server code splitting, and React-to-Preact aliasing. + +## Configuration + +The Fresh Vite plugin can be configured in `vite.config.ts`: ```ts vite.config.ts -import { defineConfig, type Plugin } from "vite"; +import { defineConfig } from "vite"; import { fresh } from "@fresh/plugin-vite"; export default defineConfig({ @@ -30,3 +36,70 @@ export default defineConfig({ ], }); ``` + +## Adding other Vite plugins + +You can use any Vite-compatible plugin alongside Fresh. The Fresh plugin should +generally come first: + +```ts vite.config.ts +import { defineConfig } from "vite"; +import { fresh } from "@fresh/plugin-vite"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [ + fresh(), + tailwindcss(), + // Add any other Vite plugins here + ], +}); +``` + +## What the plugin does + +Behind the scenes, the Fresh Vite plugin: + +- **Configures JSX** for Preact automatically (`jsxImportSource: "preact"`) +- **Aliases React to Preact** so npm packages that depend on React work out of + the box +- **Enables HMR** via [Prefresh](https://github.com/preactjs/prefresh) for fast + component reloading during development +- **Discovers islands** by scanning the islands directory and any + `islandSpecifiers` +- **Builds separate client and server bundles** using Vite's Environments + feature +- **Generates a server entry** (`_fresh/server.js`) for production deployment +- **Validates imports** to catch mistakes like importing Node.js-only modules in + browser code + +## Hot Module Replacement + +During development (`deno task dev`), the Fresh Vite plugin enables HMR so that +changes to components, islands, and CSS are reflected in the browser instantly +without a full page reload. This is powered by Prefresh, Preact's fast refresh +implementation. + +## Debugging + +To debug Vite resolution issues, run Vite with the `--debug` flag: + +```sh Terminal +deno run -A npm:vite --debug +``` + +To inspect plugin transformations, use +[`vite-plugin-inspect`](https://github.com/antfu-collective/vite-plugin-inspect): + +```ts vite.config.ts +import { defineConfig } from "vite"; +import { fresh } from "@fresh/plugin-vite"; +import inspect from "vite-plugin-inspect"; + +export default defineConfig({ + plugins: [ + fresh(), + inspect(), // Opens a UI at /__inspect to view all transformations + ], +}); +``` From 36d3d2fd479dbf7cc21852e6001803b0fcdb9cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:07:29 +0100 Subject: [PATCH 09/59] docs: add Signals concept page Signals are a core reactivity feature with zero dedicated documentation. New page covers: creating signals with useSignal, computed signals, passing signals as island props, shared state via module-level signals, and serialization behavior. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/signals.md | 133 ++++++++++++++++++++++++++++++++ docs/toc.ts | 1 + 2 files changed, 134 insertions(+) create mode 100644 docs/latest/concepts/signals.md diff --git a/docs/latest/concepts/signals.md b/docs/latest/concepts/signals.md new file mode 100644 index 00000000000..6ea1174c4d9 --- /dev/null +++ b/docs/latest/concepts/signals.md @@ -0,0 +1,133 @@ +--- +description: | + Signals provide reactive state management in Fresh islands using @preact/signals. +--- + +[Signals](https://preactjs.com/guide/v10/signals/) are Preact's reactive +primitive for managing state in islands. When a signal's value changes, any +component that reads it re-renders automatically — no need for `setState` or +manual subscriptions. + +## Creating signals + +Use `useSignal` inside a component for local state, or `signal` at module level +for shared state: + +```tsx islands/Counter.tsx +import { useSignal } from "@preact/signals"; + +export default function Counter() { + const count = useSignal(0); + + return ( +
+

Count: {count}

+ +
+ ); +} +``` + +> [info]: Signals can be rendered directly in JSX (`{count}`) without accessing +> `.value`. Preact detects the signal and subscribes to updates automatically. + +## Computed signals + +Use `computed` to derive values from other signals. Computed signals update +automatically when their dependencies change: + +```tsx islands/TemperatureConverter.tsx +import { useSignal, useComputed } from "@preact/signals"; + +export default function TemperatureConverter() { + const celsius = useSignal(20); + const fahrenheit = useComputed(() => celsius.value * 9 / 5 + 32); + + return ( +
+ celsius.value = Number(e.currentTarget.value)} + /> +

{celsius}°C = {fahrenheit}°F

+
+ ); +} +``` + +## Passing signals as props + +Signals can be passed as props to islands. Fresh automatically serializes them +on the server and reconstructs them as live signals on the client: + +```tsx routes/index.tsx +import { useSignal } from "@preact/signals"; +import Slider from "@/islands/Slider.tsx"; + +export default function Home() { + const value = useSignal(50); + return ( +
+ + +
+ ); +} +``` + +Both sliders share the same signal — moving one updates the other. When the +same signal object is passed to multiple islands, Fresh preserves the reference +so they stay synchronized. + +## Shared state across islands + +For state that needs to be shared between unrelated islands, create a signal in +a separate module: + +```ts utils/cart.ts +import { signal } from "@preact/signals"; + +export const cart = signal([]); +``` + +```tsx islands/AddToCart.tsx +import { cart } from "@/utils/cart.ts"; + +export default function AddToCart(props: { product: string }) { + return ( + + ); +} +``` + +```tsx islands/CartCount.tsx +import { cart } from "@/utils/cart.ts"; + +export default function CartCount() { + return Items in cart: {cart.value.length}; +} +``` + +Since both islands import the same module-level signal, they share the same +state automatically. See +[Sharing state between islands](/docs/examples/sharing-state-between-islands) +for more patterns. + +## Serialization + +When signals are passed as island props, Fresh handles serialization +automatically: + +- The signal's current value is extracted on the server via `.peek()` +- On the client, the value is wrapped back into a live `signal()` or + `computed()` +- Circular references and duplicate signal references are preserved + +The signal's inner value must itself be serializable (see +[Islands — Passing props](/docs/concepts/islands#passing-props-to-islands) for +the full list of supported types). diff --git a/docs/toc.ts b/docs/toc.ts index 2decfcabf1d..9524a33ccb2 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -40,6 +40,7 @@ const toc: RawTableOfContents = { ["routing", "Routing", "link:latest"], ["islands", "Islands", "link:latest"], + ["signals", "Signals", "link:latest"], ["static-files", "Static files", "link:latest"], ["file-routing", "File routing", "link:latest"], From be611a3442001d18233f7add1667f9ed2ad2aa75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:07:51 +0100 Subject: [PATCH 10/59] docs: add API Routes example page Handler-only routes (JSON APIs) are a very common pattern with no documentation. New page covers: basic JSON responses, method-specific handlers with 405 fallback, catch-all handlers, and programmatic routes. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/examples/api-routes.md | 81 ++++++++++++++++++++++++++++++ docs/toc.ts | 1 + 2 files changed, 82 insertions(+) create mode 100644 docs/latest/examples/api-routes.md diff --git a/docs/latest/examples/api-routes.md b/docs/latest/examples/api-routes.md new file mode 100644 index 00000000000..15e4cc32e12 --- /dev/null +++ b/docs/latest/examples/api-routes.md @@ -0,0 +1,81 @@ +--- +description: | + Create JSON API endpoints by defining handler-only routes without a page component. +--- + +A route that exports only `handlers` (no default component export) becomes an +API endpoint — it returns responses directly instead of rendering HTML. + +## Basic JSON API + +```ts routes/api/users.ts +import { define } from "@/utils.ts"; + +export const handlers = define.handlers({ + GET(ctx) { + const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]; + return Response.json(users); + }, +}); +``` + +A `GET /api/users` request returns: + +```json +[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}] +``` + +## Method-specific handlers + +Define different logic per HTTP method. Methods you don't define will +automatically return `405 Method Not Allowed`: + +```ts routes/api/posts/[id].ts +import { define } from "@/utils.ts"; + +export const handlers = define.handlers({ + async GET(ctx) { + const post = await db.posts.find(ctx.params.id); + if (!post) { + return Response.json({ error: "Not found" }, { status: 404 }); + } + return Response.json(post); + }, + + async PUT(ctx) { + const body = await ctx.req.json(); + const post = await db.posts.update(ctx.params.id, body); + return Response.json(post); + }, + + async DELETE(ctx) { + await db.posts.delete(ctx.params.id); + return new Response(null, { status: 204 }); + }, +}); +``` + +## Catch-all handler + +Export a single function instead of a method object to handle all HTTP methods: + +```ts routes/api/health.ts +import { define } from "@/utils.ts"; + +export const handlers = define.handlers((ctx) => { + return Response.json({ status: "ok", method: ctx.req.method }); +}); +``` + +## Programmatic API routes + +API routes can also be defined directly on the app without file-based routing: + +```ts main.ts +const app = new App() + .get("/api/time", () => Response.json({ time: new Date().toISOString() })) + .post("/api/echo", async (ctx) => { + const body = await ctx.req.text(); + return new Response(body); + }); +``` diff --git a/docs/toc.ts b/docs/toc.ts index 9524a33ccb2..25ef0ff360c 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -91,6 +91,7 @@ const toc: RawTableOfContents = { title: "Examples", link: "latest", pages: [ + ["api-routes", "API Routes", "link:latest"], ["migration-guide", "Migration Guide", "link:latest"], ["daisyui", "daisyUI", "link:latest"], ["markdown", "Rendering Markdown", "link:latest"], From 04aceb6beb2ea96257993b19e0f8be8b771b5509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:08:05 +0100 Subject: [PATCH 11/59] docs: cross-link the two Layouts pages There are two layout docs (file-based in concepts/, programmatic in advanced/) with no cross-references. Added a note at the top of each page pointing to the other with a clear distinction. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/layouts.md | 4 ++++ docs/latest/concepts/layouts.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/latest/advanced/layouts.md b/docs/latest/advanced/layouts.md index 67e3050ebef..fa043a6a956 100644 --- a/docs/latest/advanced/layouts.md +++ b/docs/latest/advanced/layouts.md @@ -2,6 +2,10 @@ description: "Create re-usable layouts across routes" --- +This page covers **programmatic layouts** defined via `app.layout()`. If you're +using file-based routing, see [Layouts (file-based)](/docs/concepts/layouts) +instead. + Layouts are plain Preact components that are inherited based on the matching pattern. When you have a section on your site where all pages share the same HTML structure and only the content changes, a layout is a neat way to abstract diff --git a/docs/latest/concepts/layouts.md b/docs/latest/concepts/layouts.md index cfb598eb128..78d497bbfca 100644 --- a/docs/latest/concepts/layouts.md +++ b/docs/latest/concepts/layouts.md @@ -3,6 +3,10 @@ description: | Add a layout to provide common meta tags, context for application sub routes, and common layout. --- +This page covers **file-based layouts** using `_layout.tsx` files. If you're +defining routes programmatically with `new App()`, see +[Layouts (programmatic)](/docs/advanced/layouts) instead. + A layout is defined in a `_layout.tsx` file in any sub directory (at any level) under the `routes/` folder. It must contain a default export that is a regular Preact component. Only one such layout is allowed per sub directory. From ecb72051b7bfd368779b7fddc6d5b3e7b4d7460f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:08:22 +0100 Subject: [PATCH 12/59] docs: resolve contradictory build commands in deployment page The deployment index listed both 'deno task build' and the legacy 'deno run -A dev.ts build'. The latter was removed by the Fresh 2 migration (dev.ts is replaced by vite.config.ts). Now shows only the current command with a note linking to the migration guide. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/deployment/index.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/latest/deployment/index.md b/docs/latest/deployment/index.md index 37955455f50..59d7780595f 100644 --- a/docs/latest/deployment/index.md +++ b/docs/latest/deployment/index.md @@ -7,10 +7,12 @@ assets for consumption in the browser. This step can be invoked by running: ```sh Terminal deno task build -# or -deno run -A dev.ts build ``` +> [info]: This runs `vite build` under the hood. If you're migrating from +> Fresh 1.x and still have a `dev.ts` file, see the +> [migration guide](/docs/examples/migration-guide) for updating your tasks. + Once completed, it will have created a `_fresh` folder in the project directory which contains the optimized assets. @@ -28,8 +30,8 @@ To run Fresh in production mode, run the `start` task: ```sh Terminal deno task start -# or -deno serve -A _fresh/server.js ``` -Fresh will automatically pick up the optimized assets in the `_fresh` directory. +This runs `deno serve -A _fresh/server.js`, which serves the built assets +directly. Fresh will automatically pick up the optimized assets in the `_fresh` +directory. From 8731665b7beaf973ebe24828c2d7038374d96b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:08:52 +0100 Subject: [PATCH 13/59] docs: expand Deno Deploy and deno compile deployment pages Deno Deploy page was 4 numbered steps (~70 words). Now covers build step configuration, environment variables, custom domains, and troubleshooting. deno compile page was ~80 words. Now covers cross-compilation, the --include flag, and known limitations. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/deployment/deno-compile.md | 35 +++++++++++++++++--- docs/latest/deployment/deno-deploy.md | 45 ++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/docs/latest/deployment/deno-compile.md b/docs/latest/deployment/deno-compile.md index 5b116c7f171..a321e8e0e9f 100644 --- a/docs/latest/deployment/deno-compile.md +++ b/docs/latest/deployment/deno-compile.md @@ -7,14 +7,41 @@ You can create a self-contained executable out of your app with the It will include all assets and dependencies. This executable can run on any platform without requiring Deno to be installed. -```sh +## Building the executable + +```sh Terminal # Build your app first -$ deno task build +deno task build # Generate self-contained executable deno compile --output my-app --include _fresh -A _fresh/compiled-entry.js ``` +The `--include _fresh` flag ensures that all built assets (JavaScript bundles, +CSS, static files) are embedded in the binary. + +## Configuration + The compiled entry supports two environment variables out of the box: -- `PORT` to set the port number (`PORT=4000 my-app`) -- `HOSTNAME` to set the host name (`HOSTNAME=0.0.0.0 my-app`) +- `PORT` to set the port number (`PORT=4000 ./my-app`) +- `HOSTNAME` to set the host name (`HOSTNAME=0.0.0.0 ./my-app`) + +## Cross-compilation + +You can compile for a different platform using the `--target` flag: + +```sh Terminal +deno compile --target x86_64-unknown-linux-gnu --output my-app --include _fresh -A _fresh/compiled-entry.js +``` + +See the +[`deno compile` documentation](https://docs.deno.com/runtime/reference/cli/compile/) +for a full list of supported targets. + +## Limitations + +- The executable size includes the Deno runtime (~50-130 MB depending on + platform) +- Dynamic imports that aren't statically analyzable may not be included +- Native npm packages with platform-specific binaries need to match the target + platform diff --git a/docs/latest/deployment/deno-deploy.md b/docs/latest/deployment/deno-deploy.md index e8b3881a2f7..601b48b04cf 100644 --- a/docs/latest/deployment/deno-deploy.md +++ b/docs/latest/deployment/deno-deploy.md @@ -7,6 +7,8 @@ The recommended way to deploy Fresh is by using previews for pull requests, collect request and HTTP metrics, as well as collect traces for you out of the box. +## Setup + 1. Log in to [Deno Deploy](https://deno.com/deploy) 1. Create a new app 1. Link your GitHub repository @@ -14,3 +16,46 @@ collect traces for you out of the box. Every time you merge into the `main` branch a new production deployment will be created. + +## Build step + +Deno Deploy runs `deno task build` automatically during deployment when the +Fresh preset is selected. Make sure your `deno.json` has the correct build task: + +```json deno.json +{ + "tasks": { + "build": "vite build", + "start": "deno serve -A _fresh/server.js" + } +} +``` + +## Environment variables + +You can set environment variables in the Deno Deploy dashboard under your +project's **Settings > Environment Variables** section. These are available at +runtime via `Deno.env.get()`. + +For variables that need to be available in island code (client-side), prefix +them with `FRESH_PUBLIC_` — see +[Environment Variables](/docs/advanced/environment-variables). + +## Custom domains + +Custom domains can be configured in the Deno Deploy dashboard under your +project's **Settings > Domains** section. Deno Deploy automatically provisions +TLS certificates for your domains. + +## Troubleshooting + +If your deployment fails to start: + +1. Ensure `deno task build` has been run (check that the Fresh preset is + selected) +2. Verify your entry point is `_fresh/server.js`, not `main.ts` — Fresh 2 + generates the server entry during the build step +3. Check the deployment logs in the Deno Deploy dashboard for specific errors + +See the [Deno Deploy documentation](https://docs.deno.com/deploy/manual/) for +more details. From 6398da6c27dfcd00ab4fc4728c1e49da8353ac13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:25:36 +0100 Subject: [PATCH 14/59] docs: add Data Fetching concept page Fresh 1.x had a dedicated data fetching page that was dropped in 2.x. New page covers: handler-to-component data flow with page(), type-safe data passing with define.handlers/define.page, async page components, middleware state, response customization, and a PageProps reference table. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/data-fetching.md | 123 ++++++++++++++++++++++++++ docs/toc.ts | 1 + 2 files changed, 124 insertions(+) create mode 100644 docs/latest/concepts/data-fetching.md diff --git a/docs/latest/concepts/data-fetching.md b/docs/latest/concepts/data-fetching.md new file mode 100644 index 00000000000..ca9e75f67c2 --- /dev/null +++ b/docs/latest/concepts/data-fetching.md @@ -0,0 +1,123 @@ +--- +description: | + Load data on the server in handlers and pass it to page components with full type safety. +--- + +Data fetching in Fresh happens on the server. Handlers load data and pass it to +page components via the `page()` helper. This keeps API keys, database +connections, and sensitive logic out of the browser. + +## Handlers and page components + +A handler fetches data and returns it with `page()`. The page component receives +it in `props.data`: + +```tsx routes/projects/[id].tsx +import { page } from "fresh"; +import { define } from "@/utils.ts"; + +interface Data { + project: { name: string; stars: number }; +} + +export const handlers = define.handlers({ + async GET(ctx) { + const project = await db.projects.findOne(ctx.params.id); + if (!project) { + throw new HttpError(404); + } + return page({ project }); + }, +}); + +export default define.page(({ data }) => { + return ( +
+

{data.project.name}

+

{data.project.stars} stars

+
+ ); +}); +``` + +The `define.page` generic links the handler's return type to +the component's props, giving you full autocompletion on `data`. + +## Setting response headers and status + +Pass options to `page()` to customize the HTTP response: + +```ts +return page(data, { + status: 201, + headers: { "Cache-Control": "public, max-age=3600" }, +}); +``` + +## Async page components + +For simpler cases, you can fetch data directly in an async component without a +separate handler: + +```tsx routes/projects/[id].tsx +import { define } from "@/utils.ts"; + +export default define.page(async (ctx) => { + const project = await db.projects.findOne(ctx.params.id); + if (!project) { + throw new HttpError(404); + } + + return ( +
+

{project.name}

+

{project.stars} stars

+
+ ); +}); +``` + +This is convenient for pages where you don't need the type-safe data bridge +between handler and component. + +## Passing state from middleware + +Middleware can set values on `ctx.state` that are available to all downstream +handlers and components: + +```ts routes/_middleware.ts +import { define } from "@/utils.ts"; + +export default define.middleware(async (ctx) => { + const session = await getSession(ctx.req); + ctx.state.user = session?.user ?? null; + return ctx.next(); +}); +``` + +```tsx routes/dashboard.tsx +import { define } from "@/utils.ts"; + +export default define.page((ctx) => { + if (!ctx.state.user) { + return ctx.redirect("/login"); + } + return

Welcome, {ctx.state.user.name}

; +}); +``` + +## What's available in page props + +Page components receive these properties: + +| Property | Type | Description | +|----------|------|-------------| +| `data` | `Data` | Data returned by the handler via `page()` | +| `url` | `URL` | The request URL | +| `params` | `Record` | Route parameters (e.g. `:id`) | +| `req` | `Request` | The original HTTP request | +| `state` | `State` | Shared state set by middleware | +| `route` | `string \| null` | The matched route pattern | +| `error` | `unknown \| null` | Caught error (on error pages) | +| `isPartial` | `boolean` | Whether this is a partial request | +| `Component` | `FunctionComponent` | Child component (in layouts) | diff --git a/docs/toc.ts b/docs/toc.ts index 25ef0ff360c..2bca6e0e1e9 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -38,6 +38,7 @@ const toc: RawTableOfContents = { ["context", "Context", "link:latest"], ["routing", "Routing", "link:latest"], + ["data-fetching", "Data Fetching", "link:latest"], ["islands", "Islands", "link:latest"], ["signals", "Signals", "link:latest"], From 40e1b4758cda82c21837c567c7d2a9b8aa102833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:26:18 +0100 Subject: [PATCH 15/59] docs: add Serialization reference page Documents what types can be passed as island props, what cannot be serialized (functions, class instances, symbols), circular reference handling, signal serialization mechanics, and common pitfalls like large props and accidental non-serializable values. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/index.md | 1 + docs/latest/advanced/serialization.md | 100 ++++++++++++++++++++++++++ docs/toc.ts | 1 + 3 files changed, 102 insertions(+) create mode 100644 docs/latest/advanced/serialization.md diff --git a/docs/latest/advanced/index.md b/docs/latest/advanced/index.md index 5ae2b1aae6a..d63ed8d761b 100644 --- a/docs/latest/advanced/index.md +++ b/docs/latest/advanced/index.md @@ -11,6 +11,7 @@ This section of the documentation describes advanced functionality of Fresh. - [Partials](/docs/advanced/partials) - Client-side partial page updates - [Forms](/docs/advanced/forms) - Handle form submissions - [Define helpers](/docs/advanced/define) - Type-safe route and middleware helpers +- [Serialization](/docs/advanced/serialization) - What types can be passed as island props - [Environment variables](/docs/advanced/environment-variables) - Use env vars in islands - [Modifying <head>](/docs/advanced/head) - Manage meta tags, titles, and styles - [Vite plugin options](/docs/advanced/vite) - Configure the Fresh Vite plugin diff --git a/docs/latest/advanced/serialization.md b/docs/latest/advanced/serialization.md new file mode 100644 index 00000000000..df31d7168f2 --- /dev/null +++ b/docs/latest/advanced/serialization.md @@ -0,0 +1,100 @@ +--- +description: | + What types can be passed as island props, how Fresh serializes data between server and client, and common pitfalls. +--- + +When Fresh renders a page on the server, island props must be serialized to +JSON and sent to the browser for hydration. Fresh uses a custom serialization +system that supports more types than standard `JSON.stringify`. + +## Supported types + +The following types can be passed as island props: + +| Type | Notes | +|------|-------| +| `string`, `number`, `boolean` | Primitive types | +| `null`, `undefined` | | +| `bigint` | | +| `NaN`, `Infinity`, `-Infinity`, `-0` | Special numeric values | +| `Array` | Including sparse arrays | +| Plain objects | Objects with string keys and serializable values | +| `Date` | | +| `URL` | | +| `RegExp` | Including flags | +| `Set` | Values must be serializable | +| `Map` | Keys and values must be serializable | +| `Uint8Array` | Binary data | +| `Signal` | From `@preact/signals` — see [Signals](/docs/concepts/signals) | +| `Computed Signal` | Read-only signals | +| JSX Elements | Server-rendered JSX passed to islands | + +## Not serializable + +The following **cannot** be passed as island props: + +- **Functions and closures** — there is no way to transfer executable code +- **Class instances** — only plain objects are supported (no custom prototypes) +- **Symbols** — not representable in JSON +- **WeakMap / WeakSet** — cannot be enumerated +- **Streams, Promises** — async values cannot be frozen for transfer + +```tsx +// WRONG — functions cannot be serialized + console.log("clicked")} /> + +// WRONG — class instance loses its prototype + +``` + +## Circular references + +Fresh handles circular references automatically. If the same object or signal +appears multiple times in the props tree, it is serialized once and all +references are restored on the client: + +```tsx +const shared = { value: 42 }; +const data = { a: shared, b: shared }; + +// `data.a` and `data.b` will reference the same object on the client + +``` + +## How signals are serialized + +When a `Signal` is detected in island props: + +1. **Server**: the signal's current value is read via `.peek()` and serialized +2. **Client**: the value is wrapped in a new `signal()` call, creating a live + reactive signal + +If the same signal object is passed to multiple islands, it is serialized once +and all islands receive the same signal instance on the client — keeping them +synchronized. + +Computed signals are serialized by reading their current value and wrapping it +in `computed(() => value)` on the client. Since the original computation +function cannot be transferred, the client-side computed signal holds a static +value. + +## Common pitfalls + +### Accidentally passing non-serializable props + +If you pass a function or class instance as a prop to an island, you'll get a +runtime error during serialization. Keep island props to plain data: + +```tsx +// Instead of passing a callback... + + +// ...pass data and handle events inside the island + +``` + +### Large props + +Every byte of serialized props is embedded in the HTML and parsed on the +client. Keep island props small — pass IDs or minimal data, and fetch the rest +client-side if needed. diff --git a/docs/toc.ts b/docs/toc.ts index 2bca6e0e1e9..dff83649750 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -57,6 +57,7 @@ const toc: RawTableOfContents = { ["partials", "Partials", "link:latest"], ["forms", "Forms", "link:latest"], ["define", "Define Helpers", "link:latest"], + ["serialization", "Serialization", "link:latest"], ["environment-variables", "Environment Variables", "link:latest"], ["head", "Modifying ", "link:latest"], ["vite", "Vite Plugin Options", "link:latest"], From 7b69ec6e14aecedaecd0dea69ff72a0bbed33b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:28:20 +0100 Subject: [PATCH 16/59] docs: add Common Patterns recipes page Collects frequently-needed patterns in one place: protected routes, URL redirects, content negotiation, cookies, query parameters, response headers, streaming, WebSockets, and timing middleware. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/examples/common-patterns.md | 202 ++++++++++++++++++++++++ docs/toc.ts | 1 + 2 files changed, 203 insertions(+) create mode 100644 docs/latest/examples/common-patterns.md diff --git a/docs/latest/examples/common-patterns.md b/docs/latest/examples/common-patterns.md new file mode 100644 index 00000000000..c6f11a2a517 --- /dev/null +++ b/docs/latest/examples/common-patterns.md @@ -0,0 +1,202 @@ +--- +description: | + Common patterns and recipes for Fresh applications: authentication, redirects, content negotiation, cookies, and more. +--- + +This page collects common patterns you'll encounter when building Fresh apps. + +## Protected routes + +Use middleware to check authentication and redirect unauthenticated users: + +```ts routes/dashboard/_middleware.ts +import { define } from "@/utils.ts"; + +export default define.middleware(async (ctx) => { + const session = await getSession(ctx.req); + if (!session) { + return ctx.redirect("/login"); + } + ctx.state.user = session.user; + return ctx.next(); +}); +``` + +All routes under `routes/dashboard/` are now protected. The user data is +available in any downstream handler or component via `ctx.state.user`. + +## Redirect old URLs + +Handle URL migrations with middleware: + +```ts routes/_middleware.ts +const REDIRECTS: Record = { + "/old-page": "/new-page", + "/blog/old-slug": "/blog/new-slug", +}; + +export default function handler(ctx) { + const redirect = REDIRECTS[ctx.url.pathname]; + if (redirect) { + return ctx.redirect(redirect, 301); + } + return ctx.next(); +} +``` + +> [info]: `ctx.redirect()` includes protection against open redirect attacks. +> Protocol-relative URLs like `//evil.com` are rejected. + +## Content negotiation + +Return different formats based on the `Accept` header: + +```ts routes/api/users/[id].ts +import { define } from "@/utils.ts"; + +export const handlers = define.handlers({ + async GET(ctx) { + const user = await db.users.find(ctx.params.id); + if (!user) { + throw new HttpError(404); + } + + const accept = ctx.req.headers.get("Accept") ?? ""; + if (accept.includes("text/html")) { + return ctx.render(); + } + return Response.json(user); + }, +}); +``` + +## Setting cookies + +Use the `@std/http` cookie utilities: + +```ts routes/_middleware.ts +import { getCookies, setCookie } from "@std/http"; + +export default async function handler(ctx) { + const cookies = getCookies(ctx.req.headers); + ctx.state.theme = cookies["theme"] ?? "light"; + + const response = await ctx.next(); + + // Set a cookie on the response + setCookie(response.headers, { + name: "theme", + value: ctx.state.theme, + path: "/", + httpOnly: true, + sameSite: "Lax", + maxAge: 60 * 60 * 24 * 365, // 1 year + }); + + return response; +} +``` + +See [Session management](/docs/examples/session-management) for a complete +session example. + +## Reading query parameters + +Access URL search params from the context: + +```ts routes/search.tsx +import { define } from "@/utils.ts"; + +export const handlers = define.handlers({ + GET(ctx) { + const query = ctx.url.searchParams.get("q") ?? ""; + const page = Number(ctx.url.searchParams.get("page") ?? "1"); + const results = search(query, page); + return page({ query, results }); + }, +}); +``` + +## Adding response headers + +Set custom headers in middleware or handlers: + +```ts routes/_middleware.ts +export default async function handler(ctx) { + const response = await ctx.next(); + response.headers.set("X-Frame-Options", "DENY"); + response.headers.set("X-Content-Type-Options", "nosniff"); + return response; +} +``` + +Or set headers on a specific route using `page()`: + +```ts +import { page } from "fresh"; + +return page(data, { + headers: { "Cache-Control": "public, max-age=3600" }, +}); +``` + +## Streaming responses + +Return a streaming response from a handler: + +```ts routes/api/stream.ts +export const handlers = define.handlers({ + GET() { + const body = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("Hello ")); + setTimeout(() => { + controller.enqueue(new TextEncoder().encode("World!")); + controller.close(); + }, 1000); + }, + }); + return new Response(body, { + headers: { "Content-Type": "text/plain" }, + }); + }, +}); +``` + +## WebSockets + +Fresh runs on Deno, so you can upgrade HTTP connections to WebSockets directly: + +```ts routes/api/ws.ts +export const handlers = define.handlers({ + GET(ctx) { + const { socket, response } = Deno.upgradeWebSocket(ctx.req); + + socket.onopen = () => { + console.log("Client connected"); + }; + socket.onmessage = (event) => { + socket.send(`Echo: ${event.data}`); + }; + socket.onclose = () => { + console.log("Client disconnected"); + }; + + return response; + }, +}); +``` + +## Timing middleware + +Measure how long request processing takes: + +```ts routes/_middleware.ts +export default async function handler(ctx) { + const start = performance.now(); + const response = await ctx.next(); + const duration = performance.now() - start; + response.headers.set("Server-Timing", `total;dur=${duration.toFixed(1)}`); + return response; +} +``` diff --git a/docs/toc.ts b/docs/toc.ts index dff83649750..047a0a42405 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -105,6 +105,7 @@ const toc: RawTableOfContents = { ], ["active-links", "Active links", "link:latest"], ["session-management", "Session management", "link:latest"], + ["common-patterns", "Common Patterns", "link:latest"], ], }, contributing: { From 00a6228e23518f9365a1c02ef1cc96ff18034a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 20:28:57 +0100 Subject: [PATCH 17/59] docs: add Architecture overview with request lifecycle diagram Fresh 1.x had a dedicated architecture page that was dropped in 2.x. New page includes an ASCII request flow diagram covering static files, middleware chain, routing, handlers, layouts, page rendering, and island hydration. Also explains server-first rendering, islands architecture, middleware onion pattern, layout inheritance, and build/deploy flow. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/architecture.md | 136 +++++++++++++++++++++++++++ docs/toc.ts | 1 + 2 files changed, 137 insertions(+) create mode 100644 docs/latest/concepts/architecture.md diff --git a/docs/latest/concepts/architecture.md b/docs/latest/concepts/architecture.md new file mode 100644 index 00000000000..04f79c1a2a3 --- /dev/null +++ b/docs/latest/concepts/architecture.md @@ -0,0 +1,136 @@ +--- +description: | + How Fresh processes requests: the flow from incoming request through middleware, routing, handlers, layouts, and island hydration. +--- + +Fresh is a server-first web framework. Pages are rendered on the server and only +the interactive parts (islands) ship JavaScript to the browser. This page +explains how a request flows through the framework. + +## Request lifecycle + +``` + Browser Request + │ + ▼ + ┌─────────────┐ + │ Static Files │──▶ Serve file directly (if match) + └─────┬───────┘ + │ (no match) + ▼ + ┌─────────────┐ + │ Middlewares │──▶ Run in registration order + │ (global) │ Can modify request/response, set state, + └─────┬───────┘ or short-circuit with a Response + │ + ▼ + ┌─────────────┐ + │ Router │──▶ Match URL pattern + HTTP method + └─────┬───────┘ Static routes checked first, then dynamic + │ + ▼ + ┌─────────────┐ + │ Middlewares │──▶ Path-specific middlewares + │ (scoped) │ + └─────┬───────┘ + │ + ▼ + ┌─────────────┐ ┌────────────┐ + │ Handler │────▶│ API route │──▶ Return Response (JSON, etc.) + └─────┬───────┘ └────────────┘ + │ (has component) + ▼ + ┌─────────────┐ + │ App Wrapper │──▶ Outer // structure + └─────┬───────┘ + │ + ▼ + ┌─────────────┐ + │ Layouts │──▶ Nested layout components (inherited) + └─────┬───────┘ + │ + ▼ + ┌─────────────┐ + │ Page │──▶ Route component with props.data + │ Component │ + └─────┬───────┘ + │ + ▼ + ┌─────────────┐ + │ HTML + JS │──▶ Server-rendered HTML sent to browser + │ Response │ Island props serialized inline + └─────────────┘ + + Browser + │ + ▼ + ┌─────────────┐ + │ Hydration │──▶ Only islands receive JavaScript + │ (Islands) │ Rest of the page stays static HTML + └─────────────┘ +``` + +## Key concepts + +### Server-first rendering + +Every page is fully rendered to HTML on the server before being sent to the +browser. This means: + +- Pages are visible immediately — no blank loading screens +- Search engines see complete content +- Pages work without JavaScript enabled + +### Islands architecture + +Fresh uses the +[islands architecture](https://jasonformat.com/islands-architecture/). Only +components in the `islands/` directory are hydrated in the browser. Everything +else is static HTML that never runs JavaScript on the client. + +This means a page with a single interactive button only ships the JavaScript for +that button — not for the entire page. + +### Middleware chain + +Middlewares execute in registration order, wrapping the handler. Each middleware +calls `ctx.next()` to pass control to the next middleware (or handler). This +creates an onion-like pattern where middlewares can act on both the request +(before `ctx.next()`) and the response (after `ctx.next()`): + +```ts +app.use(async (ctx) => { + // Before: runs on the way in + console.log("Request:", ctx.url.pathname); + + const response = await ctx.next(); + + // After: runs on the way out + console.log("Status:", response.status); + return response; +}); +``` + +### Layout inheritance + +Layouts wrap page components and are inherited from parent directories. A page +at `routes/blog/post.tsx` inherits layouts from: + +1. `routes/_layout.tsx` (root layout) +2. `routes/blog/_layout.tsx` (section layout) + +The innermost layout wraps the page component, and each outer layout wraps the +next. The app wrapper (`_app.tsx`) wraps everything. + +### Build and deploy + +Fresh uses [Vite](https://vite.dev/) to bundle island JavaScript for +production. The `deno task build` command: + +1. Discovers all islands and their dependencies +2. Bundles client-side JavaScript with code splitting +3. Generates a server entry point (`_fresh/server.js`) +4. Hashes assets for cache busting + +In production, `_fresh/server.js` serves the pre-built assets. In development, +Vite provides Hot Module Replacement for instant feedback. diff --git a/docs/toc.ts b/docs/toc.ts index 047a0a42405..8bf1a102fec 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -33,6 +33,7 @@ const toc: RawTableOfContents = { title: "Concepts", link: "latest", pages: [ + ["architecture", "Architecture", "link:latest"], ["app", "App", "link:latest"], ["middleware", "Middlewares", "link:latest"], ["context", "Context", "link:latest"], From b13260df1d433185e129eededd86ac9a3431ae60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 21:30:50 +0100 Subject: [PATCH 18/59] docs: fix remaining code and content errors - islands.md: fix "route/index.tsx" typo -> "routes/index.tsx" - middleware.md: add missing csp() to included middlewares list, fix trailingSlash -> trailingSlashes - layouts.md: fix line 88 using define.layout() for a page example -> define.page() - migration-guide.md: add missing ctx parameter in ctx.render() example - markdown.md: remove unused CONTENT variable, fix step numbering (3 not 4) - builder.md: fix inconsistent tailwind() args (remove extra app param) Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/builder.md | 2 +- docs/latest/concepts/islands.md | 2 +- docs/latest/concepts/layouts.md | 2 +- docs/latest/concepts/middleware.md | 5 +++-- docs/latest/examples/markdown.md | 9 +-------- docs/latest/examples/migration-guide.md | 2 +- 6 files changed, 8 insertions(+), 14 deletions(-) diff --git a/docs/latest/advanced/builder.md b/docs/latest/advanced/builder.md index 06cec0e3ac4..9d5c75b20f4 100644 --- a/docs/latest/advanced/builder.md +++ b/docs/latest/advanced/builder.md @@ -171,7 +171,7 @@ For more information on how to use tailwindcss, check out You can customize the tailwind plugin via the following options: ```ts dev.ts -tailwind(builder, app, { +tailwind(builder, { // Exclude certain files from processing exclude: ["/admin/**", "*.temp.css"], // Force optimization (defaults to production mode) diff --git a/docs/latest/concepts/islands.md b/docs/latest/concepts/islands.md index 2b787453820..92db7f79194 100644 --- a/docs/latest/concepts/islands.md +++ b/docs/latest/concepts/islands.md @@ -102,7 +102,7 @@ that is needed for the islands to the browser. export default (props: { foo: string }) => <>{props.foo}; ``` -```tsx route/index.tsx +```tsx routes/index.tsx import MyIsland from "../islands/my-island.tsx"; import OtherIsland from "../islands/other-island.tsx"; diff --git a/docs/latest/concepts/layouts.md b/docs/latest/concepts/layouts.md index 78d497bbfca..3febf542d1e 100644 --- a/docs/latest/concepts/layouts.md +++ b/docs/latest/concepts/layouts.md @@ -89,7 +89,7 @@ export const config: RouteConfig = { skipInheritedLayouts: true, // Skip already inherited layouts }; -export default define.layout(() => { +export default define.page(() => { return

Hello world

; }); ``` diff --git a/docs/latest/concepts/middleware.md b/docs/latest/concepts/middleware.md index 15535a8f7b1..fa65d75cd4a 100644 --- a/docs/latest/concepts/middleware.md +++ b/docs/latest/concepts/middleware.md @@ -52,8 +52,9 @@ const middleware = define.middleware(async (ctx) => { Fresh ships with the following middlewares built-in: - [cors()](/docs/plugins/cors) - Set CORS HTTP headers -- [csrf()](/docs/plugins/csrf) - Set CSRF HTTP headers -- [trailingSlash()](/docs/plugins/trailing-slashes) - Enforce trailing slashes +- [csrf()](/docs/plugins/csrf) - CSRF protection +- [csp()](/docs/plugins/csp) - Content Security Policy headers +- [trailingSlashes()](/docs/plugins/trailing-slashes) - Enforce trailing slashes ## Filesystem-based middlewares diff --git a/docs/latest/examples/markdown.md b/docs/latest/examples/markdown.md index ba7d18e928c..ad889b11227 100644 --- a/docs/latest/examples/markdown.md +++ b/docs/latest/examples/markdown.md @@ -22,18 +22,11 @@ and some interesting text here > oh look a blockquote ``` -4. Add a route that renders that file +3. Add a route that renders that file ```tsx main.ts import { CSS, render as renderMarkdown } from "@deno/gfm"; -const CONTENT = `## some heading - -and some interesting text here - -> oh look a blockquote -`; - const app = new App(); app.get("/", async (ctx) => { diff --git a/docs/latest/examples/migration-guide.md b/docs/latest/examples/migration-guide.md index fa43d59a471..de52a6a492b 100644 --- a/docs/latest/examples/migration-guide.md +++ b/docs/latest/examples/migration-guide.md @@ -292,7 +292,7 @@ To render JSX in general, use the `ctx.render()` function: ```tsx const app = new App() - .get("/", () => ctx.render(

hello

)); + .get("/", (ctx) => ctx.render(

hello

)); ``` ## `createHandler` From 033022a9bb9fe393b1758be4721755a42af4ec19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 21:31:12 +0100 Subject: [PATCH 19/59] docs: update concepts index with all sub-pages Was missing Architecture, Data Fetching, Signals, Static Files, and File Routing links. Now lists every concept sub-page with descriptions. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/index.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/latest/concepts/index.md b/docs/latest/concepts/index.md index 46f8dff28b1..533b6fb0b20 100644 --- a/docs/latest/concepts/index.md +++ b/docs/latest/concepts/index.md @@ -13,6 +13,8 @@ Fresh will boot them up in the browser and execute the relevant JavaScript. Here is an overview of the basic concepts in Fresh: +- [**Architecture**](/docs/concepts/architecture) - How requests flow through + Fresh, from middleware to islands - [**App**](/docs/concepts/app) - Holds all the information about your app, like routes, etc - [**Middleware**](/docs/concepts/middleware) - Respond to a request and return @@ -21,10 +23,17 @@ Here is an overview of the basic concepts in Fresh: called a "handler". - [**Context**](/docs/concepts/context) - Passed through every middleware. Use this to share state, trigger redirects or render HTML. -- [**Routes**](/docs/concepts/routing) - Responds to a particular URL and runs +- [**Routing**](/docs/concepts/routing) - Responds to a particular URL and runs as chain of middlewares if it matches +- [**Data Fetching**](/docs/concepts/data-fetching) - Load data on the server + and pass it to page components - [**Islands**](/docs/concepts/islands) - Render interactive Preact components on the client +- [**Signals**](/docs/concepts/signals) - Reactive state management for islands +- [**Static Files**](/docs/concepts/static-files) - Serve images, CSS, and + other assets +- [**File Routing**](/docs/concepts/file-routing) - Convention-based routing + from the filesystem Advanced concepts: From b3d32a25301fd86c0d8b8f08d7d56d5cbd883edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 21:31:25 +0100 Subject: [PATCH 20/59] docs: add next steps section to getting started page The getting started page ended abruptly after the Countdown example with no guidance on where to go next. Added links to routing, data fetching, islands, middleware, and architecture. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/getting-started/index.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/latest/getting-started/index.md b/docs/latest/getting-started/index.md index db7465a2fc5..1abd5a99992 100644 --- a/docs/latest/getting-started/index.md +++ b/docs/latest/getting-started/index.md @@ -156,3 +156,18 @@ export default define.page(() => { Now, we can see our countdown in action: ![Screenshot of the countdown component](/docs/getting-started-3-countdown.png) + +## Next steps + +Now that you have a working Fresh project, here are some things to explore: + +- [**Routing**](/docs/concepts/routing) - Learn about route patterns, dynamic + parameters, and method-specific handlers +- [**Data Fetching**](/docs/concepts/data-fetching) - Load data on the server + and pass it to page components +- [**Islands**](/docs/concepts/islands) - Understand how Fresh's partial + hydration works and what can be passed as props +- [**Middleware**](/docs/concepts/middleware) - Add authentication, logging, or + custom headers to your routes +- [**Architecture**](/docs/concepts/architecture) - See how requests flow + through the entire framework From a65125067386007e61e82f8a42c16daacff7f5a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 21:31:40 +0100 Subject: [PATCH 21/59] docs: explain (_islands) and (_components) directories in file routing These directories were shown in the project structure tree but never explained. Added a section describing what each does and why the parentheses matter. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/file-routing.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/latest/concepts/file-routing.md b/docs/latest/concepts/file-routing.md index 94faad1d0ae..c94d301fce4 100644 --- a/docs/latest/concepts/file-routing.md +++ b/docs/latest/concepts/file-routing.md @@ -46,6 +46,15 @@ Example project structure: └── index.tsx ``` +**Special directories inside `routes/`:** + +- **`(_islands)`** — Files in this directory are treated as + [islands](/docs/concepts/islands), just like files in the top-level `islands/` + folder. This lets you co-locate islands next to the routes that use them. +- **`(_components)`** — A conventional directory for non-island components that + are only used by nearby routes. Fresh does not treat these files specially — + the parentheses just prevent them from becoming routes. + File names are mapped to route patterns as follows: - File extensions are ignored. From ff6259545625357344fc721d4f5bfac98da3dc04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 21:32:44 +0100 Subject: [PATCH 22/59] docs: document previously undocumented features - static-files.md: add assetSrcSet() usage example - error-handling.md: expand HttpError docs with message param, import path, and browser availability - app.md: add Configuration section documenting basePath option - islands.md: clarify IS_BROWSER import and behavior Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/error-handling.md | 19 +++++++++++++++++-- docs/latest/concepts/app.md | 17 +++++++++++++++++ docs/latest/concepts/islands.md | 5 +++-- docs/latest/concepts/static-files.md | 15 +++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/docs/latest/advanced/error-handling.md b/docs/latest/advanced/error-handling.md index c9af64f62c0..f37f2fcdd50 100644 --- a/docs/latest/advanced/error-handling.md +++ b/docs/latest/advanced/error-handling.md @@ -71,7 +71,13 @@ middleware. Contrary to generic error pages this handler cannot be nested. ## Throwing HTTP errors If you need to bail out of execution and need to respond with a particular HTTP -error code, you can use Fresh's `HttpError` class. +error code, you can use Fresh's `HttpError` class: + +```ts +import { HttpError } from "fresh"; +``` + +`HttpError` takes a status code and an optional message: ```ts middleware/auth.ts import { HttpError } from "fresh"; @@ -84,11 +90,17 @@ async function authMiddleware(ctx) { throw new HttpError(404); } + // Forbidden with a custom message + if (!isAdmin(user)) { + throw new HttpError(403, "Admin access required"); + } + return await ctx.next(); } ``` -You can check the status code of the thrown `HttpError` in your error handler: +When an `HttpError` is thrown, Fresh catches it and invokes the error handler. +You can check the status code in your error handler: ```ts main.ts app.onError((ctx) => { @@ -100,3 +112,6 @@ app.onError((ctx) => { // ... }); ``` + +`HttpError` is also available in the browser via `fresh/runtime` for use in +island code. diff --git a/docs/latest/concepts/app.md b/docs/latest/concepts/app.md index e36ad5af8ae..a6dafb18da3 100644 --- a/docs/latest/concepts/app.md +++ b/docs/latest/concepts/app.md @@ -17,6 +17,23 @@ const app = new App() app.listen(); ``` +## Configuration + +The `App` constructor accepts an options object: + +```ts +const app = new App({ + // Serve the app from a sub-path instead of root. + // All routes will be prefixed with this path. + basePath: "/my-app", +}); +``` + +With `basePath: "/my-app"`, a route registered at `/about` will respond to +`/my-app/about`. This is useful when Fresh runs behind a reverse proxy or is +mounted alongside other apps. The base path is available in handlers via +`ctx.config.basePath`. + All items are applied from top to bottom. This means that when you defined a middleware _after_ a `.get()` handler, it won't be included. diff --git a/docs/latest/concepts/islands.md b/docs/latest/concepts/islands.md index 92db7f79194..eaf74d4e997 100644 --- a/docs/latest/concepts/islands.md +++ b/docs/latest/concepts/islands.md @@ -118,8 +118,9 @@ import OtherIsland from "../islands/other-island.tsx"; ## Rendering islands on client only When using client-only APIs, like `EventSource` or `navigator.getUserMedia`, -this component will not run on the server as it will produce an error. To fix -this use the `IS_BROWSER` flag as a guard: +the component would error during server-side rendering. Use the `IS_BROWSER` +constant from `fresh/runtime` to guard browser-only code. It is `false` on the +server and `true` in the browser: ```tsx islands/my-island.tsx import { IS_BROWSER } from "fresh/runtime"; diff --git a/docs/latest/concepts/static-files.md b/docs/latest/concepts/static-files.md index aabb6d076e1..c22e9a1c266 100644 --- a/docs/latest/concepts/static-files.md +++ b/docs/latest/concepts/static-files.md @@ -76,3 +76,18 @@ export default function About() { return View brochure; } ``` + +For `` tags with a `srcset` attribute, use `assetSrcSet()`: + +```tsx routes/gallery.tsx +import { assetSrcSet } from "fresh/runtime"; + +export default function Gallery() { + return ( + + ); +} +``` From 5a035f90fc168bd7c85b0ff4cc596cc086466996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 21:33:49 +0100 Subject: [PATCH 23/59] docs: add API Reference page listing all public exports Manual reference covering all exports from fresh, fresh/runtime, and fresh/dev entry points. Each export includes its kind (class, function, component, constant, type) and links to the relevant docs page. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/api-reference.md | 85 +++++++++++++++++++++++++++ docs/latest/advanced/index.md | 1 + docs/toc.ts | 1 + 3 files changed, 87 insertions(+) create mode 100644 docs/latest/advanced/api-reference.md diff --git a/docs/latest/advanced/api-reference.md b/docs/latest/advanced/api-reference.md new file mode 100644 index 00000000000..390100035eb --- /dev/null +++ b/docs/latest/advanced/api-reference.md @@ -0,0 +1,85 @@ +--- +description: | + Quick reference for all public exports from Fresh's entry points: fresh, fresh/runtime, and fresh/dev. +--- + +This page lists all public exports from Fresh's entry points. + +## `fresh` + +The main entry point for server-side code. + +```ts +import { App, staticFiles, createDefine, HttpError, page } from "fresh"; +``` + +| Export | Kind | Description | +|--------|------|-------------| +| `App` | Class | The main application class. See [App](/docs/concepts/app). | +| `staticFiles` | Function | Middleware for serving static files. See [Static Files](/docs/concepts/static-files). | +| `createDefine` | Function | Create type-safe `define.*` helpers. See [Define Helpers](/docs/advanced/define). | +| `page` | Function | Return data from a handler to a page component. See [Data Fetching](/docs/concepts/data-fetching). | +| `HttpError` | Class | Throw HTTP errors with status codes. See [Error Handling](/docs/advanced/error-handling). | +| `cors` | Function | CORS middleware. See [cors](/docs/plugins/cors). | +| `csrf` | Function | CSRF protection middleware. See [csrf](/docs/plugins/csrf). | +| `csp` | Function | Content Security Policy middleware. See [csp](/docs/plugins/csp). | +| `trailingSlashes` | Function | Trailing slash enforcement middleware. See [trailingSlashes](/docs/plugins/trailing-slashes). | + +**Types:** + +| Export | Description | +|--------|-------------| +| `Context` / `FreshContext` | The request context passed to all middlewares and handlers. | +| `PageProps` | Props received by page components (includes `data`, `url`, `params`, `state`, etc.). | +| `Middleware` / `MiddlewareFn` | Middleware function type. | +| `HandlerFn` | Single handler function type. | +| `HandlerByMethod` | Object with per-method handler functions. | +| `RouteHandler` | Union of `HandlerFn` and `HandlerByMethod`. | +| `PageResponse` | Return type of `page()`. | +| `RouteConfig` | Route configuration (`routeOverride`, `skipInheritedLayouts`, etc.). | +| `LayoutConfig` | Layout configuration (`skipInheritedLayouts`, `skipAppWrapper`). | +| `Define` | Type of the object returned by `createDefine()`. | +| `FreshConfig` / `ResolvedFreshConfig` | App configuration types. | +| `ListenOptions` | Options for `app.listen()`. | +| `Island` | Island component type. | +| `Method` | HTTP method union type. | +| `CORSOptions` | Options for `cors()`. | +| `CsrfOptions` | Options for `csrf()`. | +| `CSPOptions` | Options for `csp()`. | + +## `fresh/runtime` + +Shared runtime utilities for both server and client code. Safe to import in +islands. + +```ts +import { IS_BROWSER, asset, assetSrcSet, Partial, Head, HttpError } from "fresh/runtime"; +``` + +| Export | Kind | Description | +|--------|------|-------------| +| `IS_BROWSER` | Constant | `true` in the browser, `false` on the server. Use to guard browser-only code. | +| `asset` | Function | Add cache-busting query params to asset URLs. See [Static Files](/docs/concepts/static-files). | +| `assetSrcSet` | Function | Apply `asset()` to all URLs in a `srcset` string. | +| `Partial` | Component | Mark a region for partial updates. See [Partials](/docs/advanced/partials). | +| `Head` | Component | Add elements to the document ``. See [Modifying head](/docs/advanced/head). | +| `HttpError` | Class | HTTP error class (re-exported from `fresh`). | + +## `fresh/dev` + +Development and build tools. Only used in `dev.ts` (legacy) or build scripts. + +```ts +import { Builder } from "fresh/dev"; +``` + +| Export | Kind | Description | +|--------|------|-------------| +| `Builder` | Class | Pre-Vite build system (legacy). See [Builder](/docs/advanced/builder). | + +**Types:** + +| Export | Description | +|--------|-------------| +| `BuildOptions` | Options for `new Builder()`. | +| `OnTransformArgs` / `OnTransformOptions` / `TransformFn` | Build plugin hook types. | diff --git a/docs/latest/advanced/index.md b/docs/latest/advanced/index.md index d63ed8d761b..ffd4dc22c46 100644 --- a/docs/latest/advanced/index.md +++ b/docs/latest/advanced/index.md @@ -15,5 +15,6 @@ This section of the documentation describes advanced functionality of Fresh. - [Environment variables](/docs/advanced/environment-variables) - Use env vars in islands - [Modifying <head>](/docs/advanced/head) - Manage meta tags, titles, and styles - [Vite plugin options](/docs/advanced/vite) - Configure the Fresh Vite plugin +- [API Reference](/docs/advanced/api-reference) - All public exports from Fresh - [Troubleshooting](/docs/advanced/troubleshooting) - Common issues and solutions - [Builder (Legacy)](/docs/advanced/builder) - Pre-Vite build system diff --git a/docs/toc.ts b/docs/toc.ts index 8bf1a102fec..b3e84206c96 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -62,6 +62,7 @@ const toc: RawTableOfContents = { ["environment-variables", "Environment Variables", "link:latest"], ["head", "Modifying ", "link:latest"], ["vite", "Vite Plugin Options", "link:latest"], + ["api-reference", "API Reference", "link:latest"], ["troubleshooting", "Troubleshooting", "link:latest"], ["builder", "Builder (Legacy)", "link:latest"], ], From ebf194e1710d8ed5695c7a5a37031ebc2fa10418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Mar 2026 21:34:13 +0100 Subject: [PATCH 24/59] docs: update examples index and fix migration guide grammar - examples/index.md: add API Routes and Common Patterns to listing, match order to TOC - migration-guide.md: fix awkward phrasing "automatically update like" Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/examples/index.md | 10 ++++++---- docs/latest/examples/migration-guide.md | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/latest/examples/index.md b/docs/latest/examples/index.md index 819c104dcd9..8653ca819e8 100644 --- a/docs/latest/examples/index.md +++ b/docs/latest/examples/index.md @@ -6,10 +6,12 @@ description: | In this chapter of the Fresh documentation, you can find examples of features that you may like in your Fresh project. -- [Active links](./examples/active-links) +- [API Routes](./examples/api-routes) +- [Migration Guide](./examples/migration-guide) - [DaisyUI](./examples/daisyui) -- [Markdown](./examples/markdown) -- [Migration guide](./examples/migration-guide) +- [Rendering Markdown](./examples/markdown) - [Rendering raw HTML](./examples/rendering-raw-html) -- [Session management](./examples/session-management) - [Sharing state between islands](./examples/sharing-state-between-islands) +- [Active links](./examples/active-links) +- [Session management](./examples/session-management) +- [Common Patterns](./examples/common-patterns) diff --git a/docs/latest/examples/migration-guide.md b/docs/latest/examples/migration-guide.md index de52a6a492b..0e8c87cb0f1 100644 --- a/docs/latest/examples/migration-guide.md +++ b/docs/latest/examples/migration-guide.md @@ -20,7 +20,7 @@ update by running it in your project directory: deno run -Ar jsr:@fresh/update ``` -This will apply most API changes made in Fresh 2 automatically update like +This will apply most API changes made in Fresh 2 automatically, like changing `$fresh/server.ts` imports to `fresh`. ## Getting `main.ts` and `dev.ts` ready From 025b3c610b62fce6475c29bd7e1ec73bb4936096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 08:35:42 +0100 Subject: [PATCH 25/59] fmt --- docs/latest/advanced/api-reference.md | 99 +++++++++++--------- docs/latest/advanced/index.md | 15 ++- docs/latest/advanced/serialization.md | 46 ++++----- docs/latest/concepts/architecture.md | 4 +- docs/latest/concepts/data-fetching.md | 22 ++--- docs/latest/concepts/index.md | 4 +- docs/latest/concepts/islands.md | 4 +- docs/latest/concepts/layouts.md | 2 +- docs/latest/concepts/routing.md | 4 +- docs/latest/concepts/signals.md | 8 +- docs/latest/deployment/cloudflare-workers.md | 4 +- docs/latest/deployment/deno-deploy.md | 4 +- docs/latest/deployment/index.md | 4 +- docs/latest/examples/api-routes.md | 2 +- docs/latest/examples/migration-guide.md | 4 +- docs/latest/plugins/index.md | 3 +- 16 files changed, 121 insertions(+), 108 deletions(-) diff --git a/docs/latest/advanced/api-reference.md b/docs/latest/advanced/api-reference.md index 390100035eb..1a6d30db140 100644 --- a/docs/latest/advanced/api-reference.md +++ b/docs/latest/advanced/api-reference.md @@ -10,42 +10,42 @@ This page lists all public exports from Fresh's entry points. The main entry point for server-side code. ```ts -import { App, staticFiles, createDefine, HttpError, page } from "fresh"; +import { App, createDefine, HttpError, page, staticFiles } from "fresh"; ``` -| Export | Kind | Description | -|--------|------|-------------| -| `App` | Class | The main application class. See [App](/docs/concepts/app). | -| `staticFiles` | Function | Middleware for serving static files. See [Static Files](/docs/concepts/static-files). | -| `createDefine` | Function | Create type-safe `define.*` helpers. See [Define Helpers](/docs/advanced/define). | -| `page` | Function | Return data from a handler to a page component. See [Data Fetching](/docs/concepts/data-fetching). | -| `HttpError` | Class | Throw HTTP errors with status codes. See [Error Handling](/docs/advanced/error-handling). | -| `cors` | Function | CORS middleware. See [cors](/docs/plugins/cors). | -| `csrf` | Function | CSRF protection middleware. See [csrf](/docs/plugins/csrf). | -| `csp` | Function | Content Security Policy middleware. See [csp](/docs/plugins/csp). | -| `trailingSlashes` | Function | Trailing slash enforcement middleware. See [trailingSlashes](/docs/plugins/trailing-slashes). | +| Export | Kind | Description | +| ----------------- | -------- | -------------------------------------------------------------------------------------------------- | +| `App` | Class | The main application class. See [App](/docs/concepts/app). | +| `staticFiles` | Function | Middleware for serving static files. See [Static Files](/docs/concepts/static-files). | +| `createDefine` | Function | Create type-safe `define.*` helpers. See [Define Helpers](/docs/advanced/define). | +| `page` | Function | Return data from a handler to a page component. See [Data Fetching](/docs/concepts/data-fetching). | +| `HttpError` | Class | Throw HTTP errors with status codes. See [Error Handling](/docs/advanced/error-handling). | +| `cors` | Function | CORS middleware. See [cors](/docs/plugins/cors). | +| `csrf` | Function | CSRF protection middleware. See [csrf](/docs/plugins/csrf). | +| `csp` | Function | Content Security Policy middleware. See [csp](/docs/plugins/csp). | +| `trailingSlashes` | Function | Trailing slash enforcement middleware. See [trailingSlashes](/docs/plugins/trailing-slashes). | **Types:** -| Export | Description | -|--------|-------------| -| `Context` / `FreshContext` | The request context passed to all middlewares and handlers. | -| `PageProps` | Props received by page components (includes `data`, `url`, `params`, `state`, etc.). | -| `Middleware` / `MiddlewareFn` | Middleware function type. | -| `HandlerFn` | Single handler function type. | -| `HandlerByMethod` | Object with per-method handler functions. | -| `RouteHandler` | Union of `HandlerFn` and `HandlerByMethod`. | -| `PageResponse` | Return type of `page()`. | -| `RouteConfig` | Route configuration (`routeOverride`, `skipInheritedLayouts`, etc.). | -| `LayoutConfig` | Layout configuration (`skipInheritedLayouts`, `skipAppWrapper`). | -| `Define` | Type of the object returned by `createDefine()`. | -| `FreshConfig` / `ResolvedFreshConfig` | App configuration types. | -| `ListenOptions` | Options for `app.listen()`. | -| `Island` | Island component type. | -| `Method` | HTTP method union type. | -| `CORSOptions` | Options for `cors()`. | -| `CsrfOptions` | Options for `csrf()`. | -| `CSPOptions` | Options for `csp()`. | +| Export | Description | +| ------------------------------------- | ------------------------------------------------------------------------------------ | +| `Context` / `FreshContext` | The request context passed to all middlewares and handlers. | +| `PageProps` | Props received by page components (includes `data`, `url`, `params`, `state`, etc.). | +| `Middleware` / `MiddlewareFn` | Middleware function type. | +| `HandlerFn` | Single handler function type. | +| `HandlerByMethod` | Object with per-method handler functions. | +| `RouteHandler` | Union of `HandlerFn` and `HandlerByMethod`. | +| `PageResponse` | Return type of `page()`. | +| `RouteConfig` | Route configuration (`routeOverride`, `skipInheritedLayouts`, etc.). | +| `LayoutConfig` | Layout configuration (`skipInheritedLayouts`, `skipAppWrapper`). | +| `Define` | Type of the object returned by `createDefine()`. | +| `FreshConfig` / `ResolvedFreshConfig` | App configuration types. | +| `ListenOptions` | Options for `app.listen()`. | +| `Island` | Island component type. | +| `Method` | HTTP method union type. | +| `CORSOptions` | Options for `cors()`. | +| `CsrfOptions` | Options for `csrf()`. | +| `CSPOptions` | Options for `csp()`. | ## `fresh/runtime` @@ -53,17 +53,24 @@ Shared runtime utilities for both server and client code. Safe to import in islands. ```ts -import { IS_BROWSER, asset, assetSrcSet, Partial, Head, HttpError } from "fresh/runtime"; +import { + asset, + assetSrcSet, + Head, + HttpError, + IS_BROWSER, + Partial, +} from "fresh/runtime"; ``` -| Export | Kind | Description | -|--------|------|-------------| -| `IS_BROWSER` | Constant | `true` in the browser, `false` on the server. Use to guard browser-only code. | -| `asset` | Function | Add cache-busting query params to asset URLs. See [Static Files](/docs/concepts/static-files). | -| `assetSrcSet` | Function | Apply `asset()` to all URLs in a `srcset` string. | -| `Partial` | Component | Mark a region for partial updates. See [Partials](/docs/advanced/partials). | -| `Head` | Component | Add elements to the document ``. See [Modifying head](/docs/advanced/head). | -| `HttpError` | Class | HTTP error class (re-exported from `fresh`). | +| Export | Kind | Description | +| ------------- | --------- | ---------------------------------------------------------------------------------------------- | +| `IS_BROWSER` | Constant | `true` in the browser, `false` on the server. Use to guard browser-only code. | +| `asset` | Function | Add cache-busting query params to asset URLs. See [Static Files](/docs/concepts/static-files). | +| `assetSrcSet` | Function | Apply `asset()` to all URLs in a `srcset` string. | +| `Partial` | Component | Mark a region for partial updates. See [Partials](/docs/advanced/partials). | +| `Head` | Component | Add elements to the document ``. See [Modifying head](/docs/advanced/head). | +| `HttpError` | Class | HTTP error class (re-exported from `fresh`). | ## `fresh/dev` @@ -73,13 +80,13 @@ Development and build tools. Only used in `dev.ts` (legacy) or build scripts. import { Builder } from "fresh/dev"; ``` -| Export | Kind | Description | -|--------|------|-------------| +| Export | Kind | Description | +| --------- | ----- | ---------------------------------------------------------------------- | | `Builder` | Class | Pre-Vite build system (legacy). See [Builder](/docs/advanced/builder). | **Types:** -| Export | Description | -|--------|-------------| -| `BuildOptions` | Options for `new Builder()`. | -| `OnTransformArgs` / `OnTransformOptions` / `TransformFn` | Build plugin hook types. | +| Export | Description | +| -------------------------------------------------------- | ---------------------------- | +| `BuildOptions` | Options for `new Builder()`. | +| `OnTransformArgs` / `OnTransformOptions` / `TransformFn` | Build plugin hook types. | diff --git a/docs/latest/advanced/index.md b/docs/latest/advanced/index.md index ffd4dc22c46..542a9c6e107 100644 --- a/docs/latest/advanced/index.md +++ b/docs/latest/advanced/index.md @@ -10,11 +10,16 @@ This section of the documentation describes advanced functionality of Fresh. - [Error handling](/docs/advanced/error-handling) - Custom error and 404 pages - [Partials](/docs/advanced/partials) - Client-side partial page updates - [Forms](/docs/advanced/forms) - Handle form submissions -- [Define helpers](/docs/advanced/define) - Type-safe route and middleware helpers -- [Serialization](/docs/advanced/serialization) - What types can be passed as island props -- [Environment variables](/docs/advanced/environment-variables) - Use env vars in islands -- [Modifying <head>](/docs/advanced/head) - Manage meta tags, titles, and styles +- [Define helpers](/docs/advanced/define) - Type-safe route and middleware + helpers +- [Serialization](/docs/advanced/serialization) - What types can be passed as + island props +- [Environment variables](/docs/advanced/environment-variables) - Use env vars + in islands +- [Modifying <head>](/docs/advanced/head) - Manage meta tags, titles, and + styles - [Vite plugin options](/docs/advanced/vite) - Configure the Fresh Vite plugin - [API Reference](/docs/advanced/api-reference) - All public exports from Fresh -- [Troubleshooting](/docs/advanced/troubleshooting) - Common issues and solutions +- [Troubleshooting](/docs/advanced/troubleshooting) - Common issues and + solutions - [Builder (Legacy)](/docs/advanced/builder) - Pre-Vite build system diff --git a/docs/latest/advanced/serialization.md b/docs/latest/advanced/serialization.md index df31d7168f2..d74f8aeff2e 100644 --- a/docs/latest/advanced/serialization.md +++ b/docs/latest/advanced/serialization.md @@ -3,31 +3,31 @@ description: | What types can be passed as island props, how Fresh serializes data between server and client, and common pitfalls. --- -When Fresh renders a page on the server, island props must be serialized to -JSON and sent to the browser for hydration. Fresh uses a custom serialization -system that supports more types than standard `JSON.stringify`. +When Fresh renders a page on the server, island props must be serialized to JSON +and sent to the browser for hydration. Fresh uses a custom serialization system +that supports more types than standard `JSON.stringify`. ## Supported types The following types can be passed as island props: -| Type | Notes | -|------|-------| -| `string`, `number`, `boolean` | Primitive types | -| `null`, `undefined` | | -| `bigint` | | -| `NaN`, `Infinity`, `-Infinity`, `-0` | Special numeric values | -| `Array` | Including sparse arrays | -| Plain objects | Objects with string keys and serializable values | -| `Date` | | -| `URL` | | -| `RegExp` | Including flags | -| `Set` | Values must be serializable | -| `Map` | Keys and values must be serializable | -| `Uint8Array` | Binary data | -| `Signal` | From `@preact/signals` — see [Signals](/docs/concepts/signals) | -| `Computed Signal` | Read-only signals | -| JSX Elements | Server-rendered JSX passed to islands | +| Type | Notes | +| ------------------------------------ | -------------------------------------------------------------- | +| `string`, `number`, `boolean` | Primitive types | +| `null`, `undefined` | | +| `bigint` | | +| `NaN`, `Infinity`, `-Infinity`, `-0` | Special numeric values | +| `Array` | Including sparse arrays | +| Plain objects | Objects with string keys and serializable values | +| `Date` | | +| `URL` | | +| `RegExp` | Including flags | +| `Set` | Values must be serializable | +| `Map` | Keys and values must be serializable | +| `Uint8Array` | Binary data | +| `Signal` | From `@preact/signals` — see [Signals](/docs/concepts/signals) | +| `Computed Signal` | Read-only signals | +| JSX Elements | Server-rendered JSX passed to islands | ## Not serializable @@ -58,7 +58,7 @@ const shared = { value: 42 }; const data = { a: shared, b: shared }; // `data.a` and `data.b` will reference the same object on the client - +; ``` ## How signals are serialized @@ -95,6 +95,6 @@ runtime error during serialization. Keep island props to plain data: ### Large props -Every byte of serialized props is embedded in the HTML and parsed on the -client. Keep island props small — pass IDs or minimal data, and fetch the rest +Every byte of serialized props is embedded in the HTML and parsed on the client. +Keep island props small — pass IDs or minimal data, and fetch the rest client-side if needed. diff --git a/docs/latest/concepts/architecture.md b/docs/latest/concepts/architecture.md index 04f79c1a2a3..cbdd31f637c 100644 --- a/docs/latest/concepts/architecture.md +++ b/docs/latest/concepts/architecture.md @@ -124,8 +124,8 @@ next. The app wrapper (`_app.tsx`) wraps everything. ### Build and deploy -Fresh uses [Vite](https://vite.dev/) to bundle island JavaScript for -production. The `deno task build` command: +Fresh uses [Vite](https://vite.dev/) to bundle island JavaScript for production. +The `deno task build` command: 1. Discovers all islands and their dependencies 2. Bundles client-side JavaScript with code splitting diff --git a/docs/latest/concepts/data-fetching.md b/docs/latest/concepts/data-fetching.md index ca9e75f67c2..8e8043f6652 100644 --- a/docs/latest/concepts/data-fetching.md +++ b/docs/latest/concepts/data-fetching.md @@ -110,14 +110,14 @@ export default define.page((ctx) => { Page components receive these properties: -| Property | Type | Description | -|----------|------|-------------| -| `data` | `Data` | Data returned by the handler via `page()` | -| `url` | `URL` | The request URL | -| `params` | `Record` | Route parameters (e.g. `:id`) | -| `req` | `Request` | The original HTTP request | -| `state` | `State` | Shared state set by middleware | -| `route` | `string \| null` | The matched route pattern | -| `error` | `unknown \| null` | Caught error (on error pages) | -| `isPartial` | `boolean` | Whether this is a partial request | -| `Component` | `FunctionComponent` | Child component (in layouts) | +| Property | Type | Description | +| ----------- | ------------------------ | ----------------------------------------- | +| `data` | `Data` | Data returned by the handler via `page()` | +| `url` | `URL` | The request URL | +| `params` | `Record` | Route parameters (e.g. `:id`) | +| `req` | `Request` | The original HTTP request | +| `state` | `State` | Shared state set by middleware | +| `route` | `string \| null` | The matched route pattern | +| `error` | `unknown \| null` | Caught error (on error pages) | +| `isPartial` | `boolean` | Whether this is a partial request | +| `Component` | `FunctionComponent` | Child component (in layouts) | diff --git a/docs/latest/concepts/index.md b/docs/latest/concepts/index.md index 533b6fb0b20..50ba2497ed2 100644 --- a/docs/latest/concepts/index.md +++ b/docs/latest/concepts/index.md @@ -30,8 +30,8 @@ Here is an overview of the basic concepts in Fresh: - [**Islands**](/docs/concepts/islands) - Render interactive Preact components on the client - [**Signals**](/docs/concepts/signals) - Reactive state management for islands -- [**Static Files**](/docs/concepts/static-files) - Serve images, CSS, and - other assets +- [**Static Files**](/docs/concepts/static-files) - Serve images, CSS, and other + assets - [**File Routing**](/docs/concepts/file-routing) - Convention-based routing from the filesystem diff --git a/docs/latest/concepts/islands.md b/docs/latest/concepts/islands.md index eaf74d4e997..43f1ac7caa6 100644 --- a/docs/latest/concepts/islands.md +++ b/docs/latest/concepts/islands.md @@ -117,8 +117,8 @@ import OtherIsland from "../islands/other-island.tsx"; ## Rendering islands on client only -When using client-only APIs, like `EventSource` or `navigator.getUserMedia`, -the component would error during server-side rendering. Use the `IS_BROWSER` +When using client-only APIs, like `EventSource` or `navigator.getUserMedia`, the +component would error during server-side rendering. Use the `IS_BROWSER` constant from `fresh/runtime` to guard browser-only code. It is `false` on the server and `true` in the browser: diff --git a/docs/latest/concepts/layouts.md b/docs/latest/concepts/layouts.md index 3febf542d1e..1a53a128f0d 100644 --- a/docs/latest/concepts/layouts.md +++ b/docs/latest/concepts/layouts.md @@ -39,7 +39,7 @@ export default define.layout(({ Component, state }) => {
); -}) +}); ``` ## Async layouts diff --git a/docs/latest/concepts/routing.md b/docs/latest/concepts/routing.md index d232a8b6812..961507b519c 100644 --- a/docs/latest/concepts/routing.md +++ b/docs/latest/concepts/routing.md @@ -54,8 +54,8 @@ const app = new App() ## HTTP method handlers Fresh provides method-specific route registration via `.get()`, `.post()`, -`.put()`, `.delete()`, `.head()`, `.patch()`, and `.options()`. Each method -only responds to its matching HTTP verb. +`.put()`, `.delete()`, `.head()`, `.patch()`, and `.options()`. Each method only +responds to its matching HTTP verb. Use `.all()` to respond to any HTTP method: diff --git a/docs/latest/concepts/signals.md b/docs/latest/concepts/signals.md index 6ea1174c4d9..9300f51fbc9 100644 --- a/docs/latest/concepts/signals.md +++ b/docs/latest/concepts/signals.md @@ -37,7 +37,7 @@ Use `computed` to derive values from other signals. Computed signals update automatically when their dependencies change: ```tsx islands/TemperatureConverter.tsx -import { useSignal, useComputed } from "@preact/signals"; +import { useComputed, useSignal } from "@preact/signals"; export default function TemperatureConverter() { const celsius = useSignal(20); @@ -78,9 +78,9 @@ export default function Home() { } ``` -Both sliders share the same signal — moving one updates the other. When the -same signal object is passed to multiple islands, Fresh preserves the reference -so they stay synchronized. +Both sliders share the same signal — moving one updates the other. When the same +signal object is passed to multiple islands, Fresh preserves the reference so +they stay synchronized. ## Shared state across islands diff --git a/docs/latest/deployment/cloudflare-workers.md b/docs/latest/deployment/cloudflare-workers.md index 6fb901328f2..de38accb487 100644 --- a/docs/latest/deployment/cloudflare-workers.md +++ b/docs/latest/deployment/cloudflare-workers.md @@ -36,5 +36,5 @@ Check out the [Cloudflare Documentation](https://developers.cloudflare.com/workers/vite-plugin/) for further information. -> [info]: Make sure that you set the correct entrypoint in your -> `wrangler.jsonc` file. It should point to `"main": "./server.js"` +> [info]: Make sure that you set the correct entrypoint in your `wrangler.jsonc` +> file. It should point to `"main": "./server.js"` diff --git a/docs/latest/deployment/deno-deploy.md b/docs/latest/deployment/deno-deploy.md index 601b48b04cf..f48fc85dacd 100644 --- a/docs/latest/deployment/deno-deploy.md +++ b/docs/latest/deployment/deno-deploy.md @@ -4,8 +4,8 @@ description: "Deploy Fresh on Deno Deploy" The recommended way to deploy Fresh is by using [Deno Deploy](https://deno.com/deploy). It will automatically create branch -previews for pull requests, collect request and HTTP metrics, as well as -collect traces for you out of the box. +previews for pull requests, collect request and HTTP metrics, as well as collect +traces for you out of the box. ## Setup diff --git a/docs/latest/deployment/index.md b/docs/latest/deployment/index.md index 59d7780595f..d3c45f0c3c8 100644 --- a/docs/latest/deployment/index.md +++ b/docs/latest/deployment/index.md @@ -9,8 +9,8 @@ assets for consumption in the browser. This step can be invoked by running: deno task build ``` -> [info]: This runs `vite build` under the hood. If you're migrating from -> Fresh 1.x and still have a `dev.ts` file, see the +> [info]: This runs `vite build` under the hood. If you're migrating from Fresh +> 1.x and still have a `dev.ts` file, see the > [migration guide](/docs/examples/migration-guide) for updating your tasks. Once completed, it will have created a `_fresh` folder in the project directory diff --git a/docs/latest/examples/api-routes.md b/docs/latest/examples/api-routes.md index 15e4cc32e12..aaa770b1837 100644 --- a/docs/latest/examples/api-routes.md +++ b/docs/latest/examples/api-routes.md @@ -22,7 +22,7 @@ export const handlers = define.handlers({ A `GET /api/users` request returns: ```json -[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}] +[{ "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" }] ``` ## Method-specific handlers diff --git a/docs/latest/examples/migration-guide.md b/docs/latest/examples/migration-guide.md index 0e8c87cb0f1..ff5aa64d4f0 100644 --- a/docs/latest/examples/migration-guide.md +++ b/docs/latest/examples/migration-guide.md @@ -20,8 +20,8 @@ update by running it in your project directory: deno run -Ar jsr:@fresh/update ``` -This will apply most API changes made in Fresh 2 automatically, like -changing `$fresh/server.ts` imports to `fresh`. +This will apply most API changes made in Fresh 2 automatically, like changing +`$fresh/server.ts` imports to `fresh`. ## Getting `main.ts` and `dev.ts` ready diff --git a/docs/latest/plugins/index.md b/docs/latest/plugins/index.md index abdec2ff4f8..5832c6ec0f4 100644 --- a/docs/latest/plugins/index.md +++ b/docs/latest/plugins/index.md @@ -29,4 +29,5 @@ Fresh ships with the following plugins: - [cors()](/docs/plugins/cors) - Set CORS HTTP headers - [csrf()](/docs/plugins/csrf) - CSRF protection - [csp()](/docs/plugins/csp) - Content Security Policy headers -- [trailingSlashes()](/docs/plugins/trailing-slashes) - Enforce trailing slash behavior +- [trailingSlashes()](/docs/plugins/trailing-slashes) - Enforce trailing slash + behavior From 5b8af4c923c5c051ee83af800eeb1b85cf14840a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 08:39:43 +0100 Subject: [PATCH 26/59] docs: add Deno version requirements to introduction The introduction had no mention of what Deno version is needed. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/introduction/index.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/latest/introduction/index.md b/docs/latest/introduction/index.md index fd5f512bc05..dfd1f7b847d 100644 --- a/docs/latest/introduction/index.md +++ b/docs/latest/introduction/index.md @@ -19,6 +19,11 @@ const app = new App() app.listen(); ``` +## Requirements + +- [Deno](https://deno.com/) v2.1 or later +- TypeScript is supported out of the box (no configuration needed) + ## Quick Start Create a new Fresh app by running: From 37aaaed1b82ef90b34c47ede0f2b49be0b2b0ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 08:40:04 +0100 Subject: [PATCH 27/59] docs: add plugin authoring guidance to plugins page The audit noted there was no explanation of how to create custom plugins. Added a "Creating reusable plugins" section showing how to export a middleware function as a reusable plugin. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/plugins/index.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/latest/plugins/index.md b/docs/latest/plugins/index.md index 5832c6ec0f4..a6f5462b6ef 100644 --- a/docs/latest/plugins/index.md +++ b/docs/latest/plugins/index.md @@ -22,6 +22,37 @@ const addXFreshHeader = define.middleware(async (ctx) => { Learn more about [middlewares](/docs/concepts/middleware). +## Creating reusable plugins + +Since Fresh plugins are just middlewares and route handlers, creating a +reusable plugin is as simple as exporting a function that returns a middleware: + +```ts plugins/request-id.ts +import type { MiddlewareFn } from "fresh"; + +export function requestId(): MiddlewareFn<{ requestId: string }> { + return async (ctx) => { + ctx.state.requestId = crypto.randomUUID(); + const res = await ctx.next(); + res.headers.set("X-Request-Id", ctx.state.requestId); + return res; + }; +} +``` + +```ts main.ts +import { App, staticFiles } from "fresh"; +import { requestId } from "./plugins/request-id.ts"; + +const app = new App() + .use(staticFiles()) + .use(requestId()) + .fsRoutes(); +``` + +For more complex plugins, you can combine multiple middlewares, add routes, or +use the [`Builder`](/docs/advanced/builder) hooks for build-time processing. + ## Built-in plugins Fresh ships with the following plugins: From 272342a365491d2d1f62ad707775b4924d3315f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 08:40:38 +0100 Subject: [PATCH 28/59] docs: add OpenTelemetry page Fresh has built-in OTel instrumentation listed as a feature in the introduction but with no dedicated documentation. New page covers what's instrumented, how to enable tracing with Deno's built-in OTel support, and the Deno Deploy integration. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/index.md | 1 + docs/latest/advanced/opentelemetry.md | 45 +++++++++++++++++++++++++++ docs/toc.ts | 1 + 3 files changed, 47 insertions(+) create mode 100644 docs/latest/advanced/opentelemetry.md diff --git a/docs/latest/advanced/index.md b/docs/latest/advanced/index.md index 542a9c6e107..149c6a9f830 100644 --- a/docs/latest/advanced/index.md +++ b/docs/latest/advanced/index.md @@ -19,6 +19,7 @@ This section of the documentation describes advanced functionality of Fresh. - [Modifying <head>](/docs/advanced/head) - Manage meta tags, titles, and styles - [Vite plugin options](/docs/advanced/vite) - Configure the Fresh Vite plugin +- [OpenTelemetry](/docs/advanced/opentelemetry) - Built-in tracing and observability - [API Reference](/docs/advanced/api-reference) - All public exports from Fresh - [Troubleshooting](/docs/advanced/troubleshooting) - Common issues and solutions diff --git a/docs/latest/advanced/opentelemetry.md b/docs/latest/advanced/opentelemetry.md new file mode 100644 index 00000000000..0fdca4421c4 --- /dev/null +++ b/docs/latest/advanced/opentelemetry.md @@ -0,0 +1,45 @@ +--- +description: | + Fresh has built-in OpenTelemetry instrumentation for tracing requests through middleware, handlers, and rendering. +--- + +Fresh automatically instruments key operations with +[OpenTelemetry](https://opentelemetry.io/) spans, giving you visibility into +how requests flow through your application. + +## What's instrumented + +Fresh creates spans for: + +- **Middleware execution** — each middleware in the chain +- **Route handler execution** — handler function calls +- **Rendering** — server-side page rendering +- **Static file serving** — file lookups and responses +- **Lazy route loading** — dynamic imports of route modules + +## Enabling tracing + +Fresh uses the `@opentelemetry/api` package (the vendor-neutral API). Spans +are created automatically — you just need to provide an OpenTelemetry SDK and +exporter to collect them. + +If no exporter is configured, the spans are silently discarded (no performance +overhead). + +### With Deno's built-in OpenTelemetry + +Deno has +[built-in OpenTelemetry support](https://docs.deno.com/runtime/fundamentals/open_telemetry/). +Enable it with the `OTEL_DENO` environment variable: + +```sh Terminal +OTEL_DENO=true deno task start +``` + +This exports traces to an OTLP-compatible collector (configure the endpoint +with `OTEL_EXPORTER_OTLP_ENDPOINT`). + +### With Deno Deploy + +[Deno Deploy](https://deno.com/deploy) collects Fresh traces automatically +when using the Fresh preset — no configuration needed. diff --git a/docs/toc.ts b/docs/toc.ts index b3e84206c96..2939400a344 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -62,6 +62,7 @@ const toc: RawTableOfContents = { ["environment-variables", "Environment Variables", "link:latest"], ["head", "Modifying ", "link:latest"], ["vite", "Vite Plugin Options", "link:latest"], + ["opentelemetry", "OpenTelemetry", "link:latest"], ["api-reference", "API Reference", "link:latest"], ["troubleshooting", "Troubleshooting", "link:latest"], ["builder", "Builder (Legacy)", "link:latest"], From 3adb4cc22e3f9859f59f2e76587f2f3d5a8376ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 08:40:51 +0100 Subject: [PATCH 29/59] docs: add PR guidelines to contributing page The audit noted the contributing page could use PR submission guidelines. Added a section covering deno task ok, focused PRs, test expectations, and code style. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/contributing/index.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/latest/contributing/index.md b/docs/latest/contributing/index.md index 27f1992c44a..b69b0412fe7 100644 --- a/docs/latest/contributing/index.md +++ b/docs/latest/contributing/index.md @@ -91,3 +91,12 @@ convention, and require the `-A` flag. Snapshot tests are stored in Some tests may fail locally but pass in CI (`Could not find server address`, `Text file busy (os error 26)`) — these can be safely ignored. + +## Pull Requests + +- Run `deno task ok` before submitting to catch formatting, lint, and type + errors early +- Keep PRs focused — one feature or fix per PR +- Add or update tests for any behavior changes +- Follow existing code style — the repository uses `deno fmt` for formatting + and `deno lint` for linting From 715a0feffcf61f7579bc7934fa26e411a191f5a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 08:41:13 +0100 Subject: [PATCH 30/59] docs: document route css option in file routing The RouteConfig css option for loading per-route CSS files was undocumented. Added an example alongside the existing routeOverride section. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/file-routing.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/latest/concepts/file-routing.md b/docs/latest/concepts/file-routing.md index c94d301fce4..e71dc8b1546 100644 --- a/docs/latest/concepts/file-routing.md +++ b/docs/latest/concepts/file-routing.md @@ -93,6 +93,16 @@ export const config: RouteConfig = { // ... ``` +You can also load additional CSS files for a specific route: + +```ts routes/dashboard.tsx +import { RouteConfig } from "fresh"; + +export const config: RouteConfig = { + css: ["./assets/dashboard.css"], +}; +``` + ## Route Groups When working with [layouts](/docs/advanced/layouts) or From 68bc424e8bb570af51389b32d0956139a1b3a563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 08:50:00 +0100 Subject: [PATCH 31/59] docs: fix broken image references - Rename getting-started-3-cotuntdown.png -> getting-started-3-countdown.png to match the markdown reference fixed in an earlier commit - Fix 1.x partials.md referencing /docs/1.x/fresh-partial-docs.png when the file is at /docs/fresh-partial-docs.png Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/1.x/concepts/partials.md | 2 +- ...tuntdown.png => getting-started-3-countdown.png} | Bin 2 files changed, 1 insertion(+), 1 deletion(-) rename www/static/docs/{getting-started-3-cotuntdown.png => getting-started-3-countdown.png} (100%) diff --git a/docs/1.x/concepts/partials.md b/docs/1.x/concepts/partials.md index aa8af04a7f6..1b697dcfd5b 100644 --- a/docs/1.x/concepts/partials.md +++ b/docs/1.x/concepts/partials.md @@ -76,7 +76,7 @@ Let's use a typical documentation page layout as an example. It often features a main content area and a sidebar of links to switch between pages of the documentation (marked green here). -![A sketched layout of a typical documentation page with the sidebar on the left composed of green links and a main content area on the right. The main content area is labeled as Partial docs-content](/docs/1.x/fresh-partial-docs.png) +![A sketched layout of a typical documentation page with the sidebar on the left composed of green links and a main content area on the right. The main content area is labeled as Partial docs-content](/docs/fresh-partial-docs.png) The code for such a page (excluding styling) might look like this: diff --git a/www/static/docs/getting-started-3-cotuntdown.png b/www/static/docs/getting-started-3-countdown.png similarity index 100% rename from www/static/docs/getting-started-3-cotuntdown.png rename to www/static/docs/getting-started-3-countdown.png From 8f1611011ba094c6eba612fbcca46005c7aa8897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 08:50:53 +0100 Subject: [PATCH 32/59] ci: add doc image reference checker test Walks all markdown files in docs/ and verifies that every image reference (![alt](path)) points to an existing file in www/static/. Skips external URLs. Fails with a list of all broken references. Run: deno test -A docs/check_images_test.ts Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/check_images_test.ts | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/check_images_test.ts diff --git a/docs/check_images_test.ts b/docs/check_images_test.ts new file mode 100644 index 00000000000..443648964d3 --- /dev/null +++ b/docs/check_images_test.ts @@ -0,0 +1,53 @@ +/** + * Verifies that all image references in documentation markdown files + * point to existing files in www/static/. + * + * Run: deno test -A docs/check_images_test.ts + */ +import { walk } from "jsr:@std/fs@1/walk"; +import { join, resolve } from "jsr:@std/path@1"; + +const ROOT = resolve(import.meta.dirname!); +const PROJECT_ROOT = resolve(ROOT, ".."); +const DOCS_DIR = ROOT; +const STATIC_DIR = join(PROJECT_ROOT, "www", "static"); + +const IMAGE_RE = /!\[.*?\]\(([^)]+)\)/g; + +Deno.test("all doc image references point to existing files", async () => { + const errors: string[] = []; + + for await ( + const entry of walk(DOCS_DIR, { + exts: [".md", ".mdx"], + includeDirs: false, + }) + ) { + const content = await Deno.readTextFile(entry.path); + const relativePath = entry.path.slice(PROJECT_ROOT.length); + + for (const match of content.matchAll(IMAGE_RE)) { + const imgPath = match[1]; + + // Skip external URLs + if (imgPath.startsWith("http://") || imgPath.startsWith("https://")) { + continue; + } + + // Doc image paths like "/docs/foo.png" map to "www/static/docs/foo.png" + const fsPath = join(STATIC_DIR, imgPath); + + try { + await Deno.stat(fsPath); + } catch { + errors.push(`${relativePath}: image not found: ${imgPath}`); + } + } + } + + if (errors.length > 0) { + throw new Error( + `Found ${errors.length} broken image reference(s):\n\n${errors.join("\n")}`, + ); + } +}); From 2c0491ed2152d2f18d8ec4b6493bb8a577803d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 08:55:04 +0100 Subject: [PATCH 33/59] chore: format check_images_test.ts Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/check_images_test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/check_images_test.ts b/docs/check_images_test.ts index 443648964d3..e53c2ffa6d0 100644 --- a/docs/check_images_test.ts +++ b/docs/check_images_test.ts @@ -47,7 +47,9 @@ Deno.test("all doc image references point to existing files", async () => { if (errors.length > 0) { throw new Error( - `Found ${errors.length} broken image reference(s):\n\n${errors.join("\n")}`, + `Found ${errors.length} broken image reference(s):\n\n${ + errors.join("\n") + }`, ); } }); From 971ece96ae28c0691eff145de19afe1017a081d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 08:57:54 +0100 Subject: [PATCH 34/59] fmt --- docs/latest/advanced/index.md | 3 ++- docs/latest/advanced/opentelemetry.md | 16 ++++++++-------- docs/latest/contributing/index.md | 4 ++-- docs/latest/plugins/index.md | 4 ++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/latest/advanced/index.md b/docs/latest/advanced/index.md index 149c6a9f830..c5fd0aab09d 100644 --- a/docs/latest/advanced/index.md +++ b/docs/latest/advanced/index.md @@ -19,7 +19,8 @@ This section of the documentation describes advanced functionality of Fresh. - [Modifying <head>](/docs/advanced/head) - Manage meta tags, titles, and styles - [Vite plugin options](/docs/advanced/vite) - Configure the Fresh Vite plugin -- [OpenTelemetry](/docs/advanced/opentelemetry) - Built-in tracing and observability +- [OpenTelemetry](/docs/advanced/opentelemetry) - Built-in tracing and + observability - [API Reference](/docs/advanced/api-reference) - All public exports from Fresh - [Troubleshooting](/docs/advanced/troubleshooting) - Common issues and solutions diff --git a/docs/latest/advanced/opentelemetry.md b/docs/latest/advanced/opentelemetry.md index 0fdca4421c4..846e0c72562 100644 --- a/docs/latest/advanced/opentelemetry.md +++ b/docs/latest/advanced/opentelemetry.md @@ -4,8 +4,8 @@ description: | --- Fresh automatically instruments key operations with -[OpenTelemetry](https://opentelemetry.io/) spans, giving you visibility into -how requests flow through your application. +[OpenTelemetry](https://opentelemetry.io/) spans, giving you visibility into how +requests flow through your application. ## What's instrumented @@ -19,8 +19,8 @@ Fresh creates spans for: ## Enabling tracing -Fresh uses the `@opentelemetry/api` package (the vendor-neutral API). Spans -are created automatically — you just need to provide an OpenTelemetry SDK and +Fresh uses the `@opentelemetry/api` package (the vendor-neutral API). Spans are +created automatically — you just need to provide an OpenTelemetry SDK and exporter to collect them. If no exporter is configured, the spans are silently discarded (no performance @@ -36,10 +36,10 @@ Enable it with the `OTEL_DENO` environment variable: OTEL_DENO=true deno task start ``` -This exports traces to an OTLP-compatible collector (configure the endpoint -with `OTEL_EXPORTER_OTLP_ENDPOINT`). +This exports traces to an OTLP-compatible collector (configure the endpoint with +`OTEL_EXPORTER_OTLP_ENDPOINT`). ### With Deno Deploy -[Deno Deploy](https://deno.com/deploy) collects Fresh traces automatically -when using the Fresh preset — no configuration needed. +[Deno Deploy](https://deno.com/deploy) collects Fresh traces automatically when +using the Fresh preset — no configuration needed. diff --git a/docs/latest/contributing/index.md b/docs/latest/contributing/index.md index b69b0412fe7..404a4272050 100644 --- a/docs/latest/contributing/index.md +++ b/docs/latest/contributing/index.md @@ -98,5 +98,5 @@ Some tests may fail locally but pass in CI (`Could not find server address`, errors early - Keep PRs focused — one feature or fix per PR - Add or update tests for any behavior changes -- Follow existing code style — the repository uses `deno fmt` for formatting - and `deno lint` for linting +- Follow existing code style — the repository uses `deno fmt` for formatting and + `deno lint` for linting diff --git a/docs/latest/plugins/index.md b/docs/latest/plugins/index.md index a6f5462b6ef..336ed8edc48 100644 --- a/docs/latest/plugins/index.md +++ b/docs/latest/plugins/index.md @@ -24,8 +24,8 @@ Learn more about [middlewares](/docs/concepts/middleware). ## Creating reusable plugins -Since Fresh plugins are just middlewares and route handlers, creating a -reusable plugin is as simple as exporting a function that returns a middleware: +Since Fresh plugins are just middlewares and route handlers, creating a reusable +plugin is as simple as exporting a function that returns a middleware: ```ts plugins/request-id.ts import type { MiddlewareFn } from "fresh"; From 542a0827677b301f9559a2d50e392b692f4f7869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 09:09:55 +0100 Subject: [PATCH 35/59] docs: add layout overriding and multiple layouts to programmatic layouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses #3695 — documents how to register multiple layouts for different paths and how to use skipInheritedLayouts to override inherited layouts for specific routes. Closes #3695 Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/layouts.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/latest/advanced/layouts.md b/docs/latest/advanced/layouts.md index fa043a6a956..fef66e5bfbc 100644 --- a/docs/latest/advanced/layouts.md +++ b/docs/latest/advanced/layouts.md @@ -36,14 +36,37 @@ If you browse to the `/` route, Fresh will render the following HTML ``` -## Options +## Multiple layouts -Add a layout and ignore all previously inherited ones. +You can register multiple layouts for different paths. Layouts are inherited +from parent paths — a layout at `"*"` applies to all routes, and more specific +layouts are added on top: ```ts main.ts -app.layout("/foo/bar", MyComponent, { skipInheritedLayouts: true }); +const app = new App() + .layout("*", MainLayout) // Applied to all routes + .layout("/admin/*", AdminLayout) // Added on top for /admin/* routes + .get("/", (ctx) => ctx.render(

Home

)) + .get("/admin/dashboard", (ctx) => ctx.render(

Dashboard

)); ``` +For `/admin/dashboard`, both `MainLayout` and `AdminLayout` will wrap the page +component (MainLayout as the outer wrapper, AdminLayout as the inner). + +## Overriding layouts + +Use `skipInheritedLayouts` to replace all inherited layouts with a single one: + +```ts main.ts +const app = new App() + .layout("*", MainLayout) + .layout("/landing", LandingLayout, { skipInheritedLayouts: true }) + .get("/", (ctx) => ctx.render(

Home

)) // Uses MainLayout + .get("/landing", (ctx) => ctx.render(

Landing

)); // Uses only LandingLayout +``` + +## Options + Ignore the app wrapper component: ```ts main.ts From 09d1332d2f9f4c84ab5ef70f6a2946d2db67e06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 10:03:30 +0100 Subject: [PATCH 36/59] docs: clarify .listen() behavior with default project setup Explain that .listen() conflicts with deno serve / Vite dev server and document how to customize the port in the default setup. Closes #3548 Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/app.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/latest/concepts/app.md b/docs/latest/concepts/app.md index a6dafb18da3..8558eb61e9b 100644 --- a/docs/latest/concepts/app.md +++ b/docs/latest/concepts/app.md @@ -373,7 +373,8 @@ frameworks. ## `.listen()` -Spawns a server and listens for incoming connections. +Spawns a server and listens for incoming connections. This calls `Deno.serve()` +internally. ```ts const app = new App() @@ -388,3 +389,15 @@ aspects. ```ts app.listen({ port: 4000 }); ``` + +> **Important:** `.listen()` is only used when running your app directly with +> `deno run -A main.ts`. The default project setup uses `deno task dev` (Vite +> dev server) and `deno task start` (`deno serve`), which spawn their own +> servers — calling `.listen()` alongside these will create a second server and +> cause `AddrInUse` errors. +> +> To customize the port in the default setup: +> +> - **Dev:** set `server.port` in `vite.config.ts` +> - **Prod:** pass `--port` to `deno serve` in your task, e.g. +> `"start": "deno serve --port 4000 -A _fresh/server.js"` From fd43eedceeb793b92db709033b2503d28dfbc1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 10:21:09 +0100 Subject: [PATCH 37/59] docs: fix Docker deployment instructions for Fresh 2 - Add missing `deno install --allow-scripts` step before build - Remove incorrect `deno cache` step (not needed with Vite build output) - Add `deno` prefix to `serve` command in CMD - Add install step to deployment index page Ref #3500 Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/deployment/docker.md | 9 +++++++-- docs/latest/deployment/index.md | 9 ++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/latest/deployment/docker.md b/docs/latest/deployment/docker.md index 2054701119f..8dacda23b97 100644 --- a/docs/latest/deployment/docker.md +++ b/docs/latest/deployment/docker.md @@ -24,14 +24,19 @@ ENV DENO_DEPLOYMENT_ID=${GIT_REVISION} WORKDIR /app COPY . . +RUN deno install --allow-scripts RUN deno task build -RUN deno cache _fresh/server.js EXPOSE 8000 -CMD ["serve", "-A", "_fresh/server.js"] +CMD ["deno", "serve", "-A", "_fresh/server.js"] ``` +> [!NOTE] +> The `deno install --allow-scripts` step is required to populate `node_modules` +> and run any post-install scripts needed by npm packages (e.g. Tailwind CSS). +> This must happen before `deno task build`. + To build your Docker image inside of a Git repository: ```sh Terminal diff --git a/docs/latest/deployment/index.md b/docs/latest/deployment/index.md index d3c45f0c3c8..5043586f31a 100644 --- a/docs/latest/deployment/index.md +++ b/docs/latest/deployment/index.md @@ -3,7 +3,14 @@ description: "Create a production build of your app" --- When shipping an app to production, we can run a build step that optimizes -assets for consumption in the browser. This step can be invoked by running: +assets for consumption in the browser. First, make sure dependencies are +installed: + +```sh Terminal +deno install --allow-scripts +``` + +Then run the build: ```sh Terminal deno task build From dc5689646c8ffa4bf988ac8fd5135ddb6d4e6e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 10:22:02 +0100 Subject: [PATCH 38/59] docs: add tip about using root-relative URLs for static files Ref #3482 Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/static-files.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/latest/concepts/static-files.md b/docs/latest/concepts/static-files.md index c22e9a1c266..6e03a8608cd 100644 --- a/docs/latest/concepts/static-files.md +++ b/docs/latest/concepts/static-files.md @@ -37,6 +37,12 @@ import "./assets/styles.css"; - Files **referenced by URL path** (favicon.ico, fonts, robots.txt, PDFs, etc.): place in `static/` +> [!TIP] +> Always use root-relative URLs (starting with `/`) when referencing static +> files in HTML. For example, use `src="/image/photo.png"` instead of +> `src="image/photo.png"`. Relative paths resolve against the browser's current +> URL, which breaks when navigating between routes. + When you import a file in your code, Vite processes it through its build pipeline, optimizes it, and adds a content hash to the filename for cache busting. Keeping these files outside `static/` ensures they're only included From 7e9bff1fd6297dab6e0f02633c3f89c254ebc157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 10:23:52 +0100 Subject: [PATCH 39/59] docs: clarify main.ts vs main.tsx usage - Fix examples that showed JSX in main.ts (not valid without config) - Add tip explaining how to enable main.tsx with serverEntry config Closes #3476 Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/concepts/app.md | 14 +++++++++----- docs/latest/introduction/index.md | 5 ++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/latest/concepts/app.md b/docs/latest/concepts/app.md index 8558eb61e9b..e968df7ca60 100644 --- a/docs/latest/concepts/app.md +++ b/docs/latest/concepts/app.md @@ -7,16 +7,20 @@ The `App` class is the heart of Fresh and routes incoming requests to the correct middlewares. This is where routes, middlewares, layouts and more are defined. -```tsx main.ts +```ts main.ts const app = new App() .use(staticFiles()) - .get("/", () => new Response("hello")) - .get("/about", (ctx) => ctx.render(

About me

)); + .get("/", () => new Response("hello")); // Start server app.listen(); ``` +> [!TIP] +> To use JSX in your `main` file (e.g. with `ctx.render(

Hello

)`), +> rename it to `main.tsx` and set `serverEntry: "main.tsx"` in the `fresh()` +> plugin options in `vite.config.ts`. + ## Configuration The `App` constructor accepts an options object: @@ -37,7 +41,7 @@ mounted alongside other apps. The base path is available in handlers via All items are applied from top to bottom. This means that when you defined a middleware _after_ a `.get()` handler, it won't be included. -```tsx main.tsx +```ts main.ts const app = new App() .use((ctx) => { // Will be called for all middlewares @@ -48,7 +52,7 @@ const app = new App() // Will only be called for `/about` return ctx.next(); }) - .get("/about", (ctx) => ctx.render(

About me

)); + .get("/about", () => new Response("About me")); ``` ## `.use()` diff --git a/docs/latest/introduction/index.md b/docs/latest/introduction/index.md index dfd1f7b847d..0a1d77795f9 100644 --- a/docs/latest/introduction/index.md +++ b/docs/latest/introduction/index.md @@ -9,12 +9,11 @@ Fresh is a small, fast and extensible full stack web framework built on Web Standards. It's designed for building high-quality, performant, and personalized web applications. -```tsx main.tsx +```ts main.ts import { App } from "fresh"; const app = new App() - .get("/", () => new Response("hello world")) - .get("/jsx", (ctx) => ctx.render(

render JSX!

)); + .get("/", () => new Response("hello world")); app.listen(); ``` From 7ec338cdf851296ca884d8cb5f59d5dc21a12453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 10:34:15 +0100 Subject: [PATCH 40/59] docs: add catch-all redirect from /docs/canary/ to /docs/ Replaces hardcoded canary redirects with a single catch-all since canary docs have been merged into latest. Closes #3375 Co-Authored-By: Claude Opus 4.6 (1M context) --- www/routes/docs/_middleware.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/www/routes/docs/_middleware.ts b/www/routes/docs/_middleware.ts index 2a30a0ae5ed..fb49a322ac1 100644 --- a/www/routes/docs/_middleware.ts +++ b/www/routes/docs/_middleware.ts @@ -3,13 +3,24 @@ import type { Context } from "fresh"; const REDIRECTS: Record = { "/docs/getting-started/fetching-data": "/docs/getting-started/custom-handlers", - "/docs/canary/examples/modifying-the-head": "/docs/advanced/head", - "/docs/canary/concepts/builder": "/docs/advanced/builder", }; export async function handler(ctx: Context) { + const { pathname } = ctx.url; + + // Redirect /docs/canary/... to /docs/... since canary docs + // have been merged into latest + if (pathname.startsWith("/docs/canary/")) { + const rest = pathname.slice("/docs/canary".length); + const url = new URL(`/docs${rest}`, ctx.url.origin); + return new Response("", { + status: 301, + headers: { location: url.href }, + }); + } + // Redirect from old doc URLs to new ones - const redirect = REDIRECTS[ctx.url.pathname]; + const redirect = REDIRECTS[pathname]; if (redirect) { const url = new URL(redirect, ctx.url.origin); return new Response("", { From 3ac8461a16b41b3f89fa9a29c62b4cfaef7bed29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 10:41:50 +0100 Subject: [PATCH 41/59] docs: add progressive complexity code examples to homepage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shows three steps on the homepage: one-file server, adding JSX, and adding an island — demonstrating Fresh's progressive complexity. Ref #2755 Co-Authored-By: Claude Opus 4.6 (1M context) --- www/components/homepage/Simple.tsx | 66 ++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/www/components/homepage/Simple.tsx b/www/components/homepage/Simple.tsx index 07cd39c1cd0..b161d102a3b 100644 --- a/www/components/homepage/Simple.tsx +++ b/www/components/homepage/Simple.tsx @@ -1,5 +1,31 @@ import { PageSection } from "../../components/PageSection.tsx"; +const ONE_FILE_EXAMPLE = `import { App } from "fresh"; + +const app = new App() + .get("/", () => new Response("Hello, World!")); + +app.listen();`; + +const ADD_JSX_EXAMPLE = `import { App } from "fresh"; + +const app = new App() + .get("/", (ctx) => ctx.render(

Hello!

)); + +app.listen();`; + +const ADD_ISLAND_EXAMPLE = `// islands/Counter.tsx +import { useSignal } from "@preact/signals"; + +export function Counter() { + const count = useSignal(0); + return ( + + ); +}`; + export function Simple() { return ( @@ -13,6 +39,46 @@ export function Simple() { tools, conventions, and web standards.

+ +
+ + + +
); } + +function StepCard( + props: { step: string; title: string; description: string; code: string }, +) { + return ( +
+
+ + {props.step} + +

{props.title}

+
+

{props.description}

+
+        {props.code}
+      
+
+ ); +} From 60502ed329812e75db66d7275781a0576001ff96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 10:43:16 +0100 Subject: [PATCH 42/59] docs: clarify Head component dedup precedence order Explains that the last-rendered element wins, so route components override defaults set in _app.tsx. Closes #2749 Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/head.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/latest/advanced/head.md b/docs/latest/advanced/head.md index 5ab4dc58c63..7a13ade001d 100644 --- a/docs/latest/advanced/head.md +++ b/docs/latest/advanced/head.md @@ -77,5 +77,10 @@ the matching element: 5. No matching element was found, Fresh will create a new one and append it to `` +When multiple `` components render an element with the same key, the +**last one rendered wins**. Since Fresh renders top-down (app wrapper → layout → +route → page component), a route page can override defaults set in `_app.tsx` +by using the same `key` prop. + > [info]: The ``-tag is automatically deduplicated, even without a `key` > prop. From 6e1f6601d52b69f36aa04497c0f88986663dc379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 10:50:02 +0100 Subject: [PATCH 43/59] docs: add custom elements (web components) section to islands docs Shows how to register and use custom elements and third-party web component libraries inside Fresh islands. Closes #2741 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- docs/latest/concepts/islands.md | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/latest/concepts/islands.md b/docs/latest/concepts/islands.md index 43f1ac7caa6..b508236c395 100644 --- a/docs/latest/concepts/islands.md +++ b/docs/latest/concepts/islands.md @@ -134,3 +134,67 @@ export function MyIsland() { return <div></div>; } ``` + +## Using Custom Elements (Web Components) + +[Custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) +can be used in Fresh, but they must be registered client-side since +`customElements.define()` is a browser API. + +### Registering a custom element + +Use an island to register and render custom elements: + +```tsx islands/MyElement.tsx +import { useEffect } from "preact/hooks"; +import { IS_BROWSER } from "fresh/runtime"; + +export function MyElement() { + useEffect(() => { + if (customElements.get("my-greeting")) return; + + customElements.define( + "my-greeting", + class extends HTMLElement { + connectedCallback() { + const name = this.getAttribute("name") ?? "World"; + this.innerHTML = `<p>Hello, ${name}!</p>`; + } + }, + ); + }, []); + + if (!IS_BROWSER) { + return <div></div>; + } + + return <my-greeting name="Fresh" />; +} +``` + +### Using third-party web components + +Third-party web component libraries work the same way — import and register +them inside an island: + +```tsx islands/ThirdPartyElement.tsx +import { useEffect } from "preact/hooks"; +import { IS_BROWSER } from "fresh/runtime"; + +export function ShoelaceButton() { + useEffect(() => { + // Import the library's registration script + import("@shoelace-style/shoelace/dist/components/button/button.js"); + }, []); + + if (!IS_BROWSER) { + return <button>Click me</button>; + } + + return <sl-button variant="primary">Click me</sl-button>; +} +``` + +> [!TIP] +> Return a plain HTML fallback from the server-side branch (`!IS_BROWSER`) so +> the page is usable before JavaScript loads. From badf67072f0543f9b7413597009a9039756e37d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 10:55:14 +0100 Subject: [PATCH 44/59] fix: redirect direct access to recipe partial routes to homepage When recipe partial routes are accessed directly (not as a partial request), redirect to /#partials instead of serving raw HTML fragments. Closes #2559 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- www/components/homepage/PartialsSection.tsx | 2 +- www/routes/recipes/lemon-honey-tea.tsx | 22 +++++++++++++++------ www/routes/recipes/lemonade.tsx | 22 +++++++++++++++------ www/routes/recipes/lemondrop.tsx | 22 +++++++++++++++------ 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/www/components/homepage/PartialsSection.tsx b/www/components/homepage/PartialsSection.tsx index ab1db6fc683..0f7432175ac 100644 --- a/www/components/homepage/PartialsSection.tsx +++ b/www/components/homepage/PartialsSection.tsx @@ -33,7 +33,7 @@ export const Recipes = () => ( export function PartialsSection() { return ( - <PageSection> + <PageSection id="partials"> <SideBySide mdColSplit="3/2" lgColSplit="3/2" diff --git a/www/routes/recipes/lemon-honey-tea.tsx b/www/routes/recipes/lemon-honey-tea.tsx index 3f9d4b3021b..682e0bfcc45 100644 --- a/www/routes/recipes/lemon-honey-tea.tsx +++ b/www/routes/recipes/lemon-honey-tea.tsx @@ -1,12 +1,24 @@ import { Partial } from "fresh/runtime"; -import type { RouteConfig } from "fresh"; +import { define } from "../../utils/state.ts"; -export const config: RouteConfig = { +export const handler = define.handlers({ + GET(ctx) { + if (!ctx.isPartial) { + return new Response(null, { + status: 302, + headers: { location: "/#partials" }, + }); + } + return ctx.render(); + }, +}); + +export const config = { skipAppWrapper: true, skipInheritedLayouts: true, }; -export const LemonHoneyTea = () => ( +export default define.page(() => ( <Partial name="recipe"> <h2 class="mb-2 font-extrabold">Lemon-honey tea</h2> <ul> @@ -17,6 +29,4 @@ export const LemonHoneyTea = () => ( <li>Lemon peel</li> </ul> </Partial> -); - -export default LemonHoneyTea; +)); diff --git a/www/routes/recipes/lemonade.tsx b/www/routes/recipes/lemonade.tsx index c0b4be1f087..2b66a79ccd5 100644 --- a/www/routes/recipes/lemonade.tsx +++ b/www/routes/recipes/lemonade.tsx @@ -1,12 +1,24 @@ import { Partial } from "fresh/runtime"; -import type { RouteConfig } from "fresh"; +import { define } from "../../utils/state.ts"; -export const config: RouteConfig = { +export const handler = define.handlers({ + GET(ctx) { + if (!ctx.isPartial) { + return new Response(null, { + status: 302, + headers: { location: "/#partials" }, + }); + } + return ctx.render(); + }, +}); + +export const config = { skipAppWrapper: true, skipInheritedLayouts: true, }; -export const Lemonade = () => ( +export default define.page(() => ( <Partial name="recipe"> <h2 class="mb-2 font-extrabold">Lemonade</h2> <ul> @@ -17,6 +29,4 @@ export const Lemonade = () => ( <li>Ice</li> </ul> </Partial> -); - -export default Lemonade; +)); diff --git a/www/routes/recipes/lemondrop.tsx b/www/routes/recipes/lemondrop.tsx index d223adc4946..dd5075eeae2 100644 --- a/www/routes/recipes/lemondrop.tsx +++ b/www/routes/recipes/lemondrop.tsx @@ -1,12 +1,24 @@ import { Partial } from "fresh/runtime"; -import type { RouteConfig } from "fresh"; +import { define } from "../../utils/state.ts"; -export const config: RouteConfig = { +export const handler = define.handlers({ + GET(ctx) { + if (!ctx.isPartial) { + return new Response(null, { + status: 302, + headers: { location: "/#partials" }, + }); + } + return ctx.render(); + }, +}); + +export const config = { skipAppWrapper: true, skipInheritedLayouts: true, }; -export const LemonDrop = () => ( +export default define.page(() => ( <Partial name="recipe"> <h2 class="mb-2 font-extrabold">Lemondrop Martini</h2> <ul> @@ -18,6 +30,4 @@ export const LemonDrop = () => ( <li>3/4 oz simple syrup</li> </ul> </Partial> -); - -export default LemonDrop; +)); From 0c74c353391182e6e26d65388941c436029976bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 10:56:46 +0100 Subject: [PATCH 45/59] docs: rewrite cart example to avoid shared module-level signals Module-level signals are shared across all server requests, causing state leaks between users. Rewrote the cart example to create signals per-render and pass them as props, with a warning about the pitfall. Closes #2437 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../examples/sharing-state-between-islands.md | 109 ++++++++++-------- 1 file changed, 60 insertions(+), 49 deletions(-) diff --git a/docs/latest/examples/sharing-state-between-islands.md b/docs/latest/examples/sharing-state-between-islands.md index 15c9b590d9c..2b038425432 100644 --- a/docs/latest/examples/sharing-state-between-islands.md +++ b/docs/latest/examples/sharing-state-between-islands.md @@ -3,9 +3,6 @@ description: | When you need to have state shared between islands, this page provides a few recipes. --- -All of this content is lifted from this great -[example](https://github.com/lucacasonato/fresh-with-signals) by Luca. - ## Multiple Sibling Islands with Independent State Imagine we have `Counter.tsx` like this: @@ -87,87 +84,82 @@ export default function Home() { they would all use the same value. -## Independent Islands - -We can also create a `signal` in a utility file and export it for consumption -across multiple places. +## Sharing State Across Independent Islands -```ts utils/cart.ts -import { signal } from "@preact/signals"; - -export const cart = signal<string[]>([]); -``` +When islands are not rendered as siblings (e.g. one in a sidebar and one in the +main content), you can share state by creating a signal in a parent component +and passing it as a prop to each island. ```tsx islands/AddToCart.tsx +import { type Signal } from "@preact/signals"; import { Button } from "../components/Button.tsx"; -import { cart } from "../utils/cart.ts"; interface AddToCartProps { + cart: Signal<string[]>; product: string; } -// This island is used to add a product to the cart state. export default function AddToCart(props: AddToCartProps) { + const { cart, product } = props; return ( <Button - onClick={() => (cart.value = [...cart.value, props.product])} + onClick={() => (cart.value = [...cart.value, product])} class="w-full" > - Add{cart.value.includes(props.product) ? " another" : ""} "{props.product} - " to cart + Add{cart.value.includes(product) ? " another" : ""} "{product}" to cart </Button> ); } ``` ```tsx islands/Cart.tsx +import { type Signal } from "@preact/signals"; import { Button } from "../components/Button.tsx"; -import { cart } from "../utils/cart.ts"; import * as icons from "../components/Icons.tsx"; -// This island is used to display the cart contents and remove items from it. -export default function Cart() { +interface CartProps { + cart: Signal<string[]>; +} + +export default function Cart(props: CartProps) { + const { cart } = props; return ( - <h1 class="text-xl flex items-center justify-center"> - Cart - </h1> - - <ul class="w-full bg-gray-50 mt-2 p-2 rounded-sm min-h-[6.5rem]"> - {cart.value.length === 0 && ( - <li class="text-center my-4"> - <div class="text-gray-400"> - <icons.Cart class="w-8 h-8 inline-block" /> - <div> - Your cart is empty. + <div> + <h1 class="text-xl flex items-center justify-center">Cart</h1> + <ul class="w-full bg-gray-50 mt-2 p-2 rounded-sm min-h-[6.5rem]"> + {cart.value.length === 0 && ( + <li class="text-center my-4"> + <div class="text-gray-400"> + <icons.Cart class="w-8 h-8 inline-block" /> + <div>Your cart is empty.</div> </div> - </div> - </li> - )} - {cart.value.map((product, index) => ( - <CartItem product={product} index={index} /> - ))} - </ul> + </li> + )} + {cart.value.map((product, index) => ( + <CartItem cart={cart} product={product} index={index} /> + ))} + </ul> + </div> ); } interface CartItemProps { + cart: Signal<string[]>; product: string; index: number; } function CartItem(props: CartItemProps) { const remove = () => { - const newCart = [...cart.value]; + const newCart = [...props.cart.value]; newCart.splice(props.index, 1); - cart.value = newCart; + props.cart.value = newCart; }; return ( <li class="flex items-center justify-between gap-1"> <icons.Lemon class="text-gray-500" /> - <div class="flex-1"> - {props.product} - </div> + <div class="flex-1">{props.product}</div> <Button onClick={remove} aria-label="Remove" class="border-none"> <icons.X class="inline-block w-4 h-4" /> </Button> @@ -176,13 +168,32 @@ function CartItem(props: CartItemProps) { } ``` -Now we can add the islands to our site by doing the following: +Then wire them together from a route, passing the same signal to both: ```tsx routes/cart.tsx -<AddToCart product="Lemon" /> -<AddToCart product="Lime" /> -<Cart /> +import { useSignal } from "@preact/signals"; +import AddToCart from "../islands/AddToCart.tsx"; +import Cart from "../islands/Cart.tsx"; +import { define } from "../utils.ts"; + +export default define.page(function CartPage() { + const cart = useSignal<string[]>([]); + return ( + <div> + <AddToCart cart={cart} product="Lemon" /> + <AddToCart cart={cart} product="Lime" /> + <Cart cart={cart} /> + </div> + ); +}); ``` -What happens as a result? The `cart` signal is shared across the two `AddToCart` -islands _and_ the `Cart` island. +The `cart` signal is created per-render (not at module level), so each request +gets its own independent cart. Fresh serializes the signal and passes it to both +islands, keeping them in sync on the client. + +> [!CAUTION] +> Avoid creating signals at the module level (e.g. `export const cart = +> signal([])` in a utility file). Module-level state is shared across all +> requests on the server, which means different users would see the same cart. +> Always create signals inside components or handlers. From 1b9e18463f6168d691d1a6c6d784a1c081af35ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 11:12:42 +0100 Subject: [PATCH 46/59] docs: add image optimization section to static files docs Covers build-time optimization with vite-imagetools, CDN image services, and best practices for responsive images. Closes #671 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- docs/latest/concepts/static-files.md | 55 ++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/docs/latest/concepts/static-files.md b/docs/latest/concepts/static-files.md index 6e03a8608cd..57a5e4caa98 100644 --- a/docs/latest/concepts/static-files.md +++ b/docs/latest/concepts/static-files.md @@ -97,3 +97,58 @@ export default function Gallery() { ); } ``` + +## Image optimization + +Fresh does not include a built-in image optimization pipeline, but since Fresh 2 +uses Vite, you can use Vite plugins or external services to optimize images. + +### Build-time optimization with Vite + +[vite-imagetools](https://github.com/JonasKruckenberg/imagetools) lets you +import images with query parameters to resize, convert formats, and generate +`srcset` at build time: + +```ts +deno add -D npm:vite-imagetools +``` + +```ts vite.config.ts +import { defineConfig } from "vite"; +import { fresh } from "@fresh/plugin-vite"; +import { imagetools } from "vite-imagetools"; + +export default defineConfig({ + plugins: [fresh(), imagetools()], +}); +``` + +Then import optimized images directly: + +```tsx +import heroAvif from "../static/hero.jpg?format=avif&w=800"; + +export default function Page() { + return <img src={heroAvif} alt="Hero" width={800} />; +} +``` + +### CDN image services + +For dynamic optimization without a build step, use a CDN image service that +transforms images on-the-fly: + +- [Cloudflare Images](https://developers.cloudflare.com/images/) +- [imgix](https://imgix.com/) +- [Cloudinary](https://cloudinary.com/) + +These services resize, compress, and convert images to modern formats (WebP, +AVIF) based on URL parameters, with automatic caching at the edge. + +### Best practices + +- Use modern formats (WebP, AVIF) with `<picture>` fallbacks +- Provide responsive images with `srcset` and `sizes` attributes +- Set `width` and `height` on `<img>` tags to prevent layout shift +- Use `loading="lazy"` for below-the-fold images +- Use `asset()` / `assetSrcSet()` for cache-busted URLs From a00673e433fb1ec3b5e5501467610e204d62364d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 11:14:49 +0100 Subject: [PATCH 47/59] docs: add subdomain routing, proxying, and lazy island patterns - Subdomain routing via middleware + URLPattern (ref #603) - Proxying requests to upstream servers (closes #361) - Lazy-loading island content with Preact lazy() + Suspense (ref #565) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- docs/latest/examples/common-patterns.md | 77 +++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/docs/latest/examples/common-patterns.md b/docs/latest/examples/common-patterns.md index c6f11a2a517..12066229872 100644 --- a/docs/latest/examples/common-patterns.md +++ b/docs/latest/examples/common-patterns.md @@ -187,6 +187,83 @@ export const handlers = define.handlers({ }); ``` +## Subdomain routing + +Use middleware with `URLPattern` to route based on subdomains: + +```ts routes/_middleware.ts +const SUBDOMAIN_PATTERN = new URLPattern({ hostname: ":sub.example.com" }); + +export default async function handler(ctx) { + const match = SUBDOMAIN_PATTERN.exec(ctx.req.url); + if (match) { + const sub = match.hostname.groups["sub"]; + ctx.state.subdomain = sub; + + // Route to different handlers based on subdomain + if (sub === "api") { + return ctx.next(); // Let API routes handle it + } + if (sub !== "www") { + // Tenant-specific logic + ctx.state.tenant = await getTenant(sub); + } + } + return ctx.next(); +} +``` + +## Proxying requests + +Forward requests to an upstream server from a route handler: + +```ts routes/api/[...path].ts +import { define } from "@/utils.ts"; + +const UPSTREAM = "https://api.example.com"; + +export const handlers = define.handlers({ + async GET(ctx) { + const url = new URL(ctx.params.path, UPSTREAM); + url.search = ctx.url.search; + + const response = await fetch(url, { + headers: ctx.req.headers, + }); + + return new Response(response.body, { + status: response.status, + headers: response.headers, + }); + }, +}); +``` + +This is useful for proxying to backend services or working around CORS +restrictions during development. + +## Lazy-loading island content + +Use Preact's `lazy()` and `<Suspense>` to code-split heavy components inside +an island, so their JavaScript is only loaded when needed: + +```tsx islands/HeavyFeature.tsx +import { lazy, Suspense } from "preact/compat"; + +const Chart = lazy(() => import("../components/Chart.tsx")); + +export function HeavyFeature() { + return ( + <Suspense fallback={<p>Loading chart...</p>}> + <Chart /> + </Suspense> + ); +} +``` + +The `Chart` component's code is split into a separate chunk and only fetched +when `HeavyFeature` renders in the browser. + ## Timing middleware Measure how long request processing takes: From 0e07166a1587d8ed1d01f322fe5ff4a17991ad75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 11:20:14 +0100 Subject: [PATCH 48/59] chore: format docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- docs/latest/advanced/head.md | 4 ++-- docs/latest/advanced/layouts.md | 6 +++--- docs/latest/concepts/islands.md | 4 ++-- docs/latest/examples/common-patterns.md | 4 ++-- docs/latest/examples/sharing-state-between-islands.md | 9 +++++---- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/latest/advanced/head.md b/docs/latest/advanced/head.md index 7a13ade001d..e6bdd4b6309 100644 --- a/docs/latest/advanced/head.md +++ b/docs/latest/advanced/head.md @@ -79,8 +79,8 @@ the matching element: When multiple `<Head>` components render an element with the same key, the **last one rendered wins**. Since Fresh renders top-down (app wrapper → layout → -route → page component), a route page can override defaults set in `_app.tsx` -by using the same `key` prop. +route → page component), a route page can override defaults set in `_app.tsx` by +using the same `key` prop. > [info]: The `<title>`-tag is automatically deduplicated, even without a `key` > prop. diff --git a/docs/latest/advanced/layouts.md b/docs/latest/advanced/layouts.md index fef66e5bfbc..8042e2db15a 100644 --- a/docs/latest/advanced/layouts.md +++ b/docs/latest/advanced/layouts.md @@ -44,8 +44,8 @@ layouts are added on top: ```ts main.ts const app = new App() - .layout("*", MainLayout) // Applied to all routes - .layout("/admin/*", AdminLayout) // Added on top for /admin/* routes + .layout("*", MainLayout) // Applied to all routes + .layout("/admin/*", AdminLayout) // Added on top for /admin/* routes .get("/", (ctx) => ctx.render(<h1>Home</h1>)) .get("/admin/dashboard", (ctx) => ctx.render(<h1>Dashboard</h1>)); ``` @@ -61,7 +61,7 @@ Use `skipInheritedLayouts` to replace all inherited layouts with a single one: const app = new App() .layout("*", MainLayout) .layout("/landing", LandingLayout, { skipInheritedLayouts: true }) - .get("/", (ctx) => ctx.render(<h1>Home</h1>)) // Uses MainLayout + .get("/", (ctx) => ctx.render(<h1>Home</h1>)) // Uses MainLayout .get("/landing", (ctx) => ctx.render(<h1>Landing</h1>)); // Uses only LandingLayout ``` diff --git a/docs/latest/concepts/islands.md b/docs/latest/concepts/islands.md index b508236c395..021e3e36837 100644 --- a/docs/latest/concepts/islands.md +++ b/docs/latest/concepts/islands.md @@ -174,8 +174,8 @@ export function MyElement() { ### Using third-party web components -Third-party web component libraries work the same way — import and register -them inside an island: +Third-party web component libraries work the same way — import and register them +inside an island: ```tsx islands/ThirdPartyElement.tsx import { useEffect } from "preact/hooks"; diff --git a/docs/latest/examples/common-patterns.md b/docs/latest/examples/common-patterns.md index 12066229872..f54945677d8 100644 --- a/docs/latest/examples/common-patterns.md +++ b/docs/latest/examples/common-patterns.md @@ -244,8 +244,8 @@ restrictions during development. ## Lazy-loading island content -Use Preact's `lazy()` and `<Suspense>` to code-split heavy components inside -an island, so their JavaScript is only loaded when needed: +Use Preact's `lazy()` and `<Suspense>` to code-split heavy components inside an +island, so their JavaScript is only loaded when needed: ```tsx islands/HeavyFeature.tsx import { lazy, Suspense } from "preact/compat"; diff --git a/docs/latest/examples/sharing-state-between-islands.md b/docs/latest/examples/sharing-state-between-islands.md index 2b038425432..b1b8ff746a2 100644 --- a/docs/latest/examples/sharing-state-between-islands.md +++ b/docs/latest/examples/sharing-state-between-islands.md @@ -193,7 +193,8 @@ gets its own independent cart. Fresh serializes the signal and passes it to both islands, keeping them in sync on the client. > [!CAUTION] -> Avoid creating signals at the module level (e.g. `export const cart = -> signal([])` in a utility file). Module-level state is shared across all -> requests on the server, which means different users would see the same cart. -> Always create signals inside components or handlers. +> Avoid creating signals at the module level (e.g. +> `export const cart = +> signal([])` in a utility file). Module-level state is +> shared across all requests on the server, which means different users would +> see the same cart. Always create signals inside components or handlers. From cfad1d819c5c19bc041f5e6e6d126dd06ed5bbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 11:26:44 +0100 Subject: [PATCH 49/59] fix: use page() instead of ctx.render() in recipe partial routes ctx.render() requires a vnode argument; use page() to delegate rendering to the default page export instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- www/routes/recipes/lemon-honey-tea.tsx | 3 ++- www/routes/recipes/lemonade.tsx | 3 ++- www/routes/recipes/lemondrop.tsx | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/www/routes/recipes/lemon-honey-tea.tsx b/www/routes/recipes/lemon-honey-tea.tsx index 682e0bfcc45..74d1b635632 100644 --- a/www/routes/recipes/lemon-honey-tea.tsx +++ b/www/routes/recipes/lemon-honey-tea.tsx @@ -1,3 +1,4 @@ +import { page } from "fresh"; import { Partial } from "fresh/runtime"; import { define } from "../../utils/state.ts"; @@ -9,7 +10,7 @@ export const handler = define.handlers({ headers: { location: "/#partials" }, }); } - return ctx.render(); + return page(); }, }); diff --git a/www/routes/recipes/lemonade.tsx b/www/routes/recipes/lemonade.tsx index 2b66a79ccd5..fb8cc81ae6e 100644 --- a/www/routes/recipes/lemonade.tsx +++ b/www/routes/recipes/lemonade.tsx @@ -1,3 +1,4 @@ +import { page } from "fresh"; import { Partial } from "fresh/runtime"; import { define } from "../../utils/state.ts"; @@ -9,7 +10,7 @@ export const handler = define.handlers({ headers: { location: "/#partials" }, }); } - return ctx.render(); + return page(); }, }); diff --git a/www/routes/recipes/lemondrop.tsx b/www/routes/recipes/lemondrop.tsx index dd5075eeae2..063b9d9a6b9 100644 --- a/www/routes/recipes/lemondrop.tsx +++ b/www/routes/recipes/lemondrop.tsx @@ -1,3 +1,4 @@ +import { page } from "fresh"; import { Partial } from "fresh/runtime"; import { define } from "../../utils/state.ts"; @@ -9,7 +10,7 @@ export const handler = define.handlers({ headers: { location: "/#partials" }, }); } - return ctx.render(); + return page(); }, }); From 80ba4633d17355ac61856c8fda6e5e749f514d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 11:34:50 +0100 Subject: [PATCH 50/59] fix: add concepts/layouts to TOC and exclude GitHub edit links from checker - Add layouts page to concepts section in TOC (file existed but wasn't registered) - Exclude GitHub edit URLs from link checker to avoid 429 rate limiting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- docs/toc.ts | 1 + tools/check_links.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/toc.ts b/docs/toc.ts index 2939400a344..433fb9b00b5 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -44,6 +44,7 @@ const toc: RawTableOfContents = { ["islands", "Islands", "link:latest"], ["signals", "Signals", "link:latest"], ["static-files", "Static files", "link:latest"], + ["layouts", "Layouts", "link:latest"], ["file-routing", "File routing", "link:latest"], ], diff --git a/tools/check_links.ts b/tools/check_links.ts index f141ebfa7f0..7665bbfa692 100644 --- a/tools/check_links.ts +++ b/tools/check_links.ts @@ -24,6 +24,7 @@ const EXCLUDED_PREFIXES = [ "javascript:", "vscode:", "data:", + "https://github.com/denoland/fresh/edit/", ]; interface FailedLink { From 394441f776ebe86a3b0a257c80d2c2ddcf19ea6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 11:41:46 +0100 Subject: [PATCH 51/59] docs: fix code examples and docs from review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Fix handlers → handler (singular) in app.route() example 2. Fix app.onError() missing path argument 3. Fix css shown inside RouteConfig → top-level export 4. Fix variable shadowing (page shadows page() import) 5. Fix ctx.redirect() in page component → move to handler 6-8. Add missing HttpError and define imports in examples 9. Add config and info to PageProps reference table 10. Add RouteData, Lazy, MaybeLazy, ResolvedBuildConfig to API reference 11. Clarify layout nesting order in architecture docs 12. Add note explaining useSignal in server-rendered route for signals Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- docs/latest/advanced/api-reference.md | 11 +++++++---- docs/latest/concepts/app.md | 6 +++--- docs/latest/concepts/architecture.md | 5 +++-- docs/latest/concepts/data-fetching.md | 18 ++++++++++++++---- docs/latest/concepts/file-routing.md | 9 +++------ docs/latest/concepts/signals.md | 6 ++++++ docs/latest/examples/common-patterns.md | 8 ++++++-- 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/docs/latest/advanced/api-reference.md b/docs/latest/advanced/api-reference.md index 1a6d30db140..31c91029b98 100644 --- a/docs/latest/advanced/api-reference.md +++ b/docs/latest/advanced/api-reference.md @@ -43,6 +43,8 @@ import { App, createDefine, HttpError, page, staticFiles } from "fresh"; | `ListenOptions` | Options for `app.listen()`. | | `Island` | Island component type. | | `Method` | HTTP method union type. | +| `RouteData` | Data type returned by route handlers via `page()`. | +| `Lazy` / `MaybeLazy` | Utility types for lazily-loaded routes and middleware. | | `CORSOptions` | Options for `cors()`. | | `CsrfOptions` | Options for `csrf()`. | | `CSPOptions` | Options for `csp()`. | @@ -86,7 +88,8 @@ import { Builder } from "fresh/dev"; **Types:** -| Export | Description | -| -------------------------------------------------------- | ---------------------------- | -| `BuildOptions` | Options for `new Builder()`. | -| `OnTransformArgs` / `OnTransformOptions` / `TransformFn` | Build plugin hook types. | +| Export | Description | +| -------------------------------------------------------- | ----------------------------- | +| `BuildOptions` | Options for `new Builder()`. | +| `ResolvedBuildConfig` | Resolved build configuration. | +| `OnTransformArgs` / `OnTransformOptions` / `TransformFn` | Build plugin hook types. | diff --git a/docs/latest/concepts/app.md b/docs/latest/concepts/app.md index e968df7ca60..5cb4864f791 100644 --- a/docs/latest/concepts/app.md +++ b/docs/latest/concepts/app.md @@ -283,7 +283,7 @@ Register a route with a component and optional handlers for data loading. ```tsx app.route("/about", { component: (ctx) => <h1>About {ctx.data.name}</h1>, - handlers: { + handler: { GET(ctx) { return page({ name: "Fresh" }); }, @@ -317,8 +317,8 @@ app.onError("*", (ctx) => { Setting a route with a component: ```tsx -app.onError((ctx) => { - return ctx.render(<h1>Oops! Something went wrong.</h1>); +app.onError("*", { + component: (ctx) => <h1>Oops! {String(ctx.error)}</h1>, }); ``` diff --git a/docs/latest/concepts/architecture.md b/docs/latest/concepts/architecture.md index cbdd31f637c..d94bab327b1 100644 --- a/docs/latest/concepts/architecture.md +++ b/docs/latest/concepts/architecture.md @@ -119,8 +119,9 @@ at `routes/blog/post.tsx` inherits layouts from: 1. `routes/_layout.tsx` (root layout) 2. `routes/blog/_layout.tsx` (section layout) -The innermost layout wraps the page component, and each outer layout wraps the -next. The app wrapper (`_app.tsx`) wraps everything. +Layouts nest from the outside in: the root layout is outermost, each deeper +layout wraps closer to the page, and the innermost layout directly wraps the +page component. The app wrapper (`_app.tsx`) wraps everything. ### Build and deploy diff --git a/docs/latest/concepts/data-fetching.md b/docs/latest/concepts/data-fetching.md index 8e8043f6652..ffb2b966656 100644 --- a/docs/latest/concepts/data-fetching.md +++ b/docs/latest/concepts/data-fetching.md @@ -13,7 +13,7 @@ A handler fetches data and returns it with `page()`. The page component receives it in `props.data`: ```tsx routes/projects/[id].tsx -import { page } from "fresh"; +import { HttpError, page } from "fresh"; import { define } from "@/utils.ts"; interface Data { @@ -60,6 +60,7 @@ For simpler cases, you can fetch data directly in an async component without a separate handler: ```tsx routes/projects/[id].tsx +import { HttpError } from "fresh"; import { define } from "@/utils.ts"; export default define.page(async (ctx) => { @@ -96,12 +97,19 @@ export default define.middleware(async (ctx) => { ``` ```tsx routes/dashboard.tsx +import { page } from "fresh"; import { define } from "@/utils.ts"; +export const handler = define.handlers({ + GET(ctx) { + if (!ctx.state.user) { + return ctx.redirect("/login"); + } + return page(); + }, +}); + export default define.page((ctx) => { - if (!ctx.state.user) { - return ctx.redirect("/login"); - } return <h1>Welcome, {ctx.state.user.name}</h1>; }); ``` @@ -117,7 +125,9 @@ Page components receive these properties: | `params` | `Record<string, string>` | Route parameters (e.g. `:id`) | | `req` | `Request` | The original HTTP request | | `state` | `State` | Shared state set by middleware | +| `config` | `ResolvedFreshConfig` | The resolved Fresh configuration | | `route` | `string \| null` | The matched route pattern | +| `info` | `Deno.ServeHandlerInfo` | Server connection info | | `error` | `unknown \| null` | Caught error (on error pages) | | `isPartial` | `boolean` | Whether this is a partial request | | `Component` | `FunctionComponent` | Child component (in layouts) | diff --git a/docs/latest/concepts/file-routing.md b/docs/latest/concepts/file-routing.md index e71dc8b1546..18f2f7ec8b0 100644 --- a/docs/latest/concepts/file-routing.md +++ b/docs/latest/concepts/file-routing.md @@ -93,14 +93,11 @@ export const config: RouteConfig = { // ... ``` -You can also load additional CSS files for a specific route: +You can also load additional CSS files for a specific route by exporting a `css` +array. This is a top-level export, separate from `config`: ```ts routes/dashboard.tsx -import { RouteConfig } from "fresh"; - -export const config: RouteConfig = { - css: ["./assets/dashboard.css"], -}; +export const css = ["./assets/dashboard.css"]; ``` ## Route Groups diff --git a/docs/latest/concepts/signals.md b/docs/latest/concepts/signals.md index 9300f51fbc9..1e8b6b4680e 100644 --- a/docs/latest/concepts/signals.md +++ b/docs/latest/concepts/signals.md @@ -82,6 +82,12 @@ Both sliders share the same signal — moving one updates the other. When the sa signal object is passed to multiple islands, Fresh preserves the reference so they stay synchronized. +> [!NOTE] +> Using `useSignal` in a route component (not an island) is intentional here. +> The signal is created during server rendering, serialized into the HTML, and +> reconstructed as a live signal on the client. This is how Fresh shares +> reactive state between multiple islands on the same page. + ## Shared state across islands For state that needs to be shared between unrelated islands, create a signal in diff --git a/docs/latest/examples/common-patterns.md b/docs/latest/examples/common-patterns.md index f54945677d8..8ac42eef7b0 100644 --- a/docs/latest/examples/common-patterns.md +++ b/docs/latest/examples/common-patterns.md @@ -52,6 +52,7 @@ export default function handler(ctx) { Return different formats based on the `Accept` header: ```ts routes/api/users/[id].ts +import { HttpError } from "fresh"; import { define } from "@/utils.ts"; export const handlers = define.handlers({ @@ -105,13 +106,14 @@ session example. Access URL search params from the context: ```ts routes/search.tsx +import { page } from "fresh"; import { define } from "@/utils.ts"; export const handlers = define.handlers({ GET(ctx) { const query = ctx.url.searchParams.get("q") ?? ""; - const page = Number(ctx.url.searchParams.get("page") ?? "1"); - const results = search(query, page); + const pageNum = Number(ctx.url.searchParams.get("page") ?? "1"); + const results = search(query, pageNum); return page({ query, results }); }, }); @@ -145,6 +147,8 @@ return page(data, { Return a streaming response from a handler: ```ts routes/api/stream.ts +import { define } from "@/utils.ts"; + export const handlers = define.handlers({ GET() { const body = new ReadableStream({ From 92f074465d1cb6dc80843c37ab6ff4b9093f67a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 12:00:34 +0100 Subject: [PATCH 52/59] docs: fix remaining review issues - Fix app.onError() missing path arg in error-handling.md - Add missing HttpError import in error-handling.md - Add missing define imports in WebSocket, subdomain, timing examples - Standardize on `handler` (singular) for all export names - Convert all untyped middleware to define.middleware() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- docs/latest/advanced/error-handling.md | 4 ++- docs/latest/concepts/data-fetching.md | 8 ++--- docs/latest/examples/common-patterns.md | 43 ++++++++++++++++--------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/docs/latest/advanced/error-handling.md b/docs/latest/advanced/error-handling.md index f37f2fcdd50..94382f8c4b6 100644 --- a/docs/latest/advanced/error-handling.md +++ b/docs/latest/advanced/error-handling.md @@ -103,7 +103,9 @@ When an `HttpError` is thrown, Fresh catches it and invokes the error handler. You can check the status code in your error handler: ```ts main.ts -app.onError((ctx) => { +import { HttpError } from "fresh"; + +app.onError("*", (ctx) => { if (ctx.error instanceof HttpError) { const status = ctx.error.status; return new Response("oops", { status }); diff --git a/docs/latest/concepts/data-fetching.md b/docs/latest/concepts/data-fetching.md index ffb2b966656..cf49abeeaea 100644 --- a/docs/latest/concepts/data-fetching.md +++ b/docs/latest/concepts/data-fetching.md @@ -20,7 +20,7 @@ interface Data { project: { name: string; stars: number }; } -export const handlers = define.handlers({ +export const handler = define.handlers({ async GET(ctx) { const project = await db.projects.findOne(ctx.params.id); if (!project) { @@ -30,7 +30,7 @@ export const handlers = define.handlers({ }, }); -export default define.page<typeof handlers>(({ data }) => { +export default define.page<typeof handler>(({ data }) => { return ( <div> <h1>{data.project.name}</h1> @@ -40,8 +40,8 @@ export default define.page<typeof handlers>(({ data }) => { }); ``` -The `define.page<typeof handlers>` generic links the handler's return type to -the component's props, giving you full autocompletion on `data`. +The `define.page<typeof handler>` generic links the handler's return type to the +component's props, giving you full autocompletion on `data`. ## Setting response headers and status diff --git a/docs/latest/examples/common-patterns.md b/docs/latest/examples/common-patterns.md index 8ac42eef7b0..c60dba1e209 100644 --- a/docs/latest/examples/common-patterns.md +++ b/docs/latest/examples/common-patterns.md @@ -30,18 +30,20 @@ available in any downstream handler or component via `ctx.state.user`. Handle URL migrations with middleware: ```ts routes/_middleware.ts +import { define } from "@/utils.ts"; + const REDIRECTS: Record<string, string> = { "/old-page": "/new-page", "/blog/old-slug": "/blog/new-slug", }; -export default function handler(ctx) { +export default define.middleware((ctx) => { const redirect = REDIRECTS[ctx.url.pathname]; if (redirect) { return ctx.redirect(redirect, 301); } return ctx.next(); -} +}); ``` > [info]: `ctx.redirect()` includes protection against open redirect attacks. @@ -55,7 +57,7 @@ Return different formats based on the `Accept` header: import { HttpError } from "fresh"; import { define } from "@/utils.ts"; -export const handlers = define.handlers({ +export const handler = define.handlers({ async GET(ctx) { const user = await db.users.find(ctx.params.id); if (!user) { @@ -77,8 +79,9 @@ Use the `@std/http` cookie utilities: ```ts routes/_middleware.ts import { getCookies, setCookie } from "@std/http"; +import { define } from "@/utils.ts"; -export default async function handler(ctx) { +export default define.middleware(async (ctx) => { const cookies = getCookies(ctx.req.headers); ctx.state.theme = cookies["theme"] ?? "light"; @@ -95,7 +98,7 @@ export default async function handler(ctx) { }); return response; -} +}); ``` See [Session management](/docs/examples/session-management) for a complete @@ -109,7 +112,7 @@ Access URL search params from the context: import { page } from "fresh"; import { define } from "@/utils.ts"; -export const handlers = define.handlers({ +export const handler = define.handlers({ GET(ctx) { const query = ctx.url.searchParams.get("q") ?? ""; const pageNum = Number(ctx.url.searchParams.get("page") ?? "1"); @@ -121,15 +124,17 @@ export const handlers = define.handlers({ ## Adding response headers -Set custom headers in middleware or handlers: +Set custom headers in middleware: ```ts routes/_middleware.ts -export default async function handler(ctx) { +import { define } from "@/utils.ts"; + +export default define.middleware(async (ctx) => { const response = await ctx.next(); response.headers.set("X-Frame-Options", "DENY"); response.headers.set("X-Content-Type-Options", "nosniff"); return response; -} +}); ``` Or set headers on a specific route using `page()`: @@ -149,7 +154,7 @@ Return a streaming response from a handler: ```ts routes/api/stream.ts import { define } from "@/utils.ts"; -export const handlers = define.handlers({ +export const handler = define.handlers({ GET() { const body = new ReadableStream({ start(controller) { @@ -172,7 +177,9 @@ export const handlers = define.handlers({ Fresh runs on Deno, so you can upgrade HTTP connections to WebSockets directly: ```ts routes/api/ws.ts -export const handlers = define.handlers({ +import { define } from "@/utils.ts"; + +export const handler = define.handlers({ GET(ctx) { const { socket, response } = Deno.upgradeWebSocket(ctx.req); @@ -196,9 +203,11 @@ export const handlers = define.handlers({ Use middleware with `URLPattern` to route based on subdomains: ```ts routes/_middleware.ts +import { define } from "@/utils.ts"; + const SUBDOMAIN_PATTERN = new URLPattern({ hostname: ":sub.example.com" }); -export default async function handler(ctx) { +export default define.middleware(async (ctx) => { const match = SUBDOMAIN_PATTERN.exec(ctx.req.url); if (match) { const sub = match.hostname.groups["sub"]; @@ -214,7 +223,7 @@ export default async function handler(ctx) { } } return ctx.next(); -} +}); ``` ## Proxying requests @@ -226,7 +235,7 @@ import { define } from "@/utils.ts"; const UPSTREAM = "https://api.example.com"; -export const handlers = define.handlers({ +export const handler = define.handlers({ async GET(ctx) { const url = new URL(ctx.params.path, UPSTREAM); url.search = ctx.url.search; @@ -273,11 +282,13 @@ when `HeavyFeature` renders in the browser. Measure how long request processing takes: ```ts routes/_middleware.ts -export default async function handler(ctx) { +import { define } from "@/utils.ts"; + +export default define.middleware(async (ctx) => { const start = performance.now(); const response = await ctx.next(); const duration = performance.now() - start; response.headers.set("Server-Timing", `total;dur=${duration.toFixed(1)}`); return response; -} +}); ``` From cb0127205b240fff7d45f47b0780a84443322154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 12:03:46 +0100 Subject: [PATCH 53/59] docs: link to JSR auto-generated API docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- docs/latest/advanced/api-reference.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/latest/advanced/api-reference.md b/docs/latest/advanced/api-reference.md index 31c91029b98..9aa76c05efb 100644 --- a/docs/latest/advanced/api-reference.md +++ b/docs/latest/advanced/api-reference.md @@ -5,6 +5,9 @@ description: | This page lists all public exports from Fresh's entry points. +> [info]: You can also explore Fresh's full API documentation on JSR: +> [`@fresh/core`](https://jsr.io/@fresh/core/doc) + ## `fresh` The main entry point for server-side code. From 16147069a7691a8dd185d46661847129afd9012c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 15:21:42 +0100 Subject: [PATCH 54/59] docs: replace ASCII art with HTML/CSS flowchart in architecture page The ASCII art request lifecycle diagram had broken box alignment. Replace it with an inline HTML/CSS flowchart that renders cleanly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- docs/latest/concepts/architecture.md | 145 ++++++++++++++++----------- 1 file changed, 85 insertions(+), 60 deletions(-) diff --git a/docs/latest/concepts/architecture.md b/docs/latest/concepts/architecture.md index d94bab327b1..66c72600a09 100644 --- a/docs/latest/concepts/architecture.md +++ b/docs/latest/concepts/architecture.md @@ -9,66 +9,91 @@ explains how a request flows through the framework. ## Request lifecycle -``` - Browser Request - │ - ▼ - ┌─────────────┐ - │ Static Files │──▶ Serve file directly (if match) - └─────┬───────┘ - │ (no match) - ▼ - ┌─────────────┐ - │ Middlewares │──▶ Run in registration order - │ (global) │ Can modify request/response, set state, - └─────┬───────┘ or short-circuit with a Response - │ - ▼ - ┌─────────────┐ - │ Router │──▶ Match URL pattern + HTTP method - └─────┬───────┘ Static routes checked first, then dynamic - │ - ▼ - ┌─────────────┐ - │ Middlewares │──▶ Path-specific middlewares - │ (scoped) │ - └─────┬───────┘ - │ - ▼ - ┌─────────────┐ ┌────────────┐ - │ Handler │────▶│ API route │──▶ Return Response (JSON, etc.) - └─────┬───────┘ └────────────┘ - │ (has component) - ▼ - ┌─────────────┐ - │ App Wrapper │──▶ Outer <html>/<head>/<body> structure - └─────┬───────┘ - │ - ▼ - ┌─────────────┐ - │ Layouts │──▶ Nested layout components (inherited) - └─────┬───────┘ - │ - ▼ - ┌─────────────┐ - │ Page │──▶ Route component with props.data - │ Component │ - └─────┬───────┘ - │ - ▼ - ┌─────────────┐ - │ HTML + JS │──▶ Server-rendered HTML sent to browser - │ Response │ Island props serialized inline - └─────────────┘ - - Browser - │ - ▼ - ┌─────────────┐ - │ Hydration │──▶ Only islands receive JavaScript - │ (Islands) │ Rest of the page stays static HTML - └─────────────┘ -``` +<div style="max-width:620px;font-family:system-ui,-apple-system,sans-serif;font-size:14px;color:#1a1a1a;"> + <style> + .flow-step{display:flex;align-items:flex-start;gap:16px} + .flow-box{flex-shrink:0;width:200px;padding:10px 14px;border:1.5px solid #333;border-radius:6px;background:#f8f9fa;text-align:center} + .flow-box strong{display:block;font-size:14px} + .flow-box small{font-size:11px;color:#777} + .flow-box.accent{background:#eef6ff} + .flow-box.green{background:#f0faf0} + .flow-desc{padding-top:10px;font-size:12.5px;color:#555;line-height:1.5} + .flow-arrow{text-align:center;width:200px;color:#666;font-size:18px;line-height:1;padding:4px 0} + .flow-arrow-label{text-align:center;width:200px;padding:2px 0} + .flow-arrow-label em{font-size:11px;color:#777} + .flow-divider{margin:12px 0 8px;padding-top:8px;border-top:1px dashed #e0e0e0;font-size:11px;color:#999;font-weight:600;letter-spacing:0.5px;text-transform:uppercase} + </style> + <!-- Browser Request --> + <div class="flow-step"> + <div class="flow-box accent"><strong>Browser Request</strong></div> + </div> + <div class="flow-arrow">↓</div> + <!-- Static Files --> + <div class="flow-step"> + <div class="flow-box"><strong>Static Files</strong></div> + <div class="flow-desc">Serve file directly if path matches a static asset</div> + </div> + <div class="flow-arrow-label"><em>no match</em></div> + <div class="flow-arrow">↓</div> + <!-- Global Middleware --> + <div class="flow-step"> + <div class="flow-box"><strong>Global Middleware</strong><small>runs in registration order</small></div> + <div class="flow-desc">Can modify request/response, set state, or short‑circuit with a Response</div> + </div> + <div class="flow-arrow">↓</div> + <!-- Router --> + <div class="flow-step"> + <div class="flow-box"><strong>Router</strong></div> + <div class="flow-desc">Match URL pattern + HTTP method. Static routes checked first, then dynamic.</div> + </div> + <div class="flow-arrow">↓</div> + <!-- Scoped Middleware --> + <div class="flow-step"> + <div class="flow-box"><strong>Scoped Middleware</strong></div> + <div class="flow-desc">Path‑specific middleware</div> + </div> + <div class="flow-arrow">↓</div> + <!-- Handler --> + <div class="flow-step"> + <div class="flow-box"><strong>Handler</strong></div> + <div class="flow-desc">API route? Return Response directly (JSON, redirect, etc.)</div> + </div> + <div class="flow-arrow-label"><em>has component</em></div> + <div class="flow-arrow">↓</div> + <!-- Rendering section --> + <div class="flow-divider">Rendering</div> + <!-- App Wrapper --> + <div class="flow-step"> + <div class="flow-box"><strong>App Wrapper</strong></div> + <div class="flow-desc">Outer <html>/<head>/<body> structure</div> + </div> + <div class="flow-arrow">↓</div> + <!-- Layouts --> + <div class="flow-step"> + <div class="flow-box"><strong>Layouts</strong></div> + <div class="flow-desc">Nested layout components (inherited from parent directories)</div> + </div> + <div class="flow-arrow">↓</div> + <!-- Page Component --> + <div class="flow-step"> + <div class="flow-box"><strong>Page Component</strong></div> + <div class="flow-desc">Route component receives props.data from handler</div> + </div> + <div class="flow-arrow">↓</div> + <!-- HTML Response --> + <div class="flow-step"> + <div class="flow-box accent"><strong>HTML + JS Response</strong><small>sent to browser</small></div> + <div class="flow-desc">Server‑rendered HTML with island props serialized inline</div> + </div> + <!-- Client section --> + <div class="flow-divider">Client (Browser)</div> + <div class="flow-arrow">↓</div> + <!-- Hydration --> + <div class="flow-step"> + <div class="flow-box green"><strong>Island Hydration</strong><small>only islands receive JS</small></div> + <div class="flow-desc">Rest of the page stays static HTML. No JavaScript for non‑island components.</div> + </div> +</div> ## Key concepts From 6a1b5079afe439d5112e55d6457170179b611650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sat, 28 Mar 2026 16:24:03 +0100 Subject: [PATCH 55/59] docs: replace em dashes, add cross-links, update introduction and architecture - Replace em dashes with hyphens across all changed docs - Add ~49 cross-links between doc pages for better discoverability - Restore JSX example on introduction page, remove Requirements section - Rework "Where to host" section, add AI agent mention to "When to use" - Expand scoped middleware description in architecture diagram Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- docs/latest/advanced/define.md | 4 +- docs/latest/advanced/environment-variables.md | 2 +- docs/latest/advanced/error-handling.md | 5 ++- docs/latest/advanced/head.md | 11 +++--- docs/latest/advanced/layouts.md | 4 +- docs/latest/advanced/opentelemetry.md | 17 ++++---- docs/latest/advanced/serialization.md | 27 ++++++------- docs/latest/advanced/troubleshooting.md | 2 +- docs/latest/advanced/vite.md | 3 +- docs/latest/concepts/app.md | 10 ++--- docs/latest/concepts/architecture.md | 39 +++++++++++++------ docs/latest/concepts/context.md | 8 ++-- docs/latest/concepts/data-fetching.md | 4 +- docs/latest/concepts/file-routing.md | 10 ++--- docs/latest/concepts/islands.md | 9 +++-- docs/latest/concepts/layouts.md | 2 +- docs/latest/concepts/middleware.md | 5 ++- docs/latest/concepts/routing.md | 2 +- docs/latest/concepts/signals.md | 14 +++---- docs/latest/concepts/static-files.md | 6 +-- docs/latest/contributing/index.md | 6 +-- docs/latest/deployment/cloudflare-workers.md | 3 +- docs/latest/deployment/deno-deploy.md | 8 ++-- docs/latest/deployment/docker.md | 14 ++++--- docs/latest/deployment/index.md | 4 +- docs/latest/examples/api-routes.md | 5 ++- docs/latest/examples/common-patterns.md | 3 +- docs/latest/examples/migration-guide.md | 9 +++-- docs/latest/examples/rendering-raw-html.md | 4 +- .../examples/sharing-state-between-islands.md | 4 +- docs/latest/getting-started/index.md | 6 +-- docs/latest/introduction/index.md | 30 +++++++------- docs/latest/plugins/index.md | 4 +- tools/check_links.ts | 4 +- www/components/homepage/PartialsSection.tsx | 2 +- 35 files changed, 161 insertions(+), 129 deletions(-) diff --git a/docs/latest/advanced/define.md b/docs/latest/advanced/define.md index 73a9d2eefa2..afe587caa79 100644 --- a/docs/latest/advanced/define.md +++ b/docs/latest/advanced/define.md @@ -1,6 +1,6 @@ --- description: | - Define helpers are a less TypeScripty way to declare middlewares, routes and layouts + Define helpers are a less TypeScripty way to declare [middlewares](/docs/concepts/middleware), routes and [layouts](/docs/concepts/layouts) --- Define helpers can be used to shorten the amount of types you have to type @@ -72,7 +72,7 @@ export default define.page<typeof handler>((props) => { }); ``` -There is also a `define.layout()` helper for layouts: +There is also a `define.layout()` helper for [layouts](/docs/concepts/layouts): ```tsx export default define.layout((props) => { diff --git a/docs/latest/advanced/environment-variables.md b/docs/latest/advanced/environment-variables.md index 463fbf39aff..b5c527f78f6 100644 --- a/docs/latest/advanced/environment-variables.md +++ b/docs/latest/advanced/environment-variables.md @@ -9,7 +9,7 @@ see [how to use Environment Variables in Deno](https://docs.deno.com/runtime/reference/env_variables/). On top of that Fresh automatically inlines all environment variables whose names -start with `FRESH_PUBLIC_` during bundling of islands. +start with `FRESH_PUBLIC_` during bundling of [islands](/docs/concepts/islands). > [info]: This inlining step occurs when building the app (`deno task build`). > Environment variables inside islands cannot be read at runtime. diff --git a/docs/latest/advanced/error-handling.md b/docs/latest/advanced/error-handling.md index 94382f8c4b6..6056d7fd45e 100644 --- a/docs/latest/advanced/error-handling.md +++ b/docs/latest/advanced/error-handling.md @@ -66,7 +66,8 @@ const app = new App() ``` Accessing an unknown route like `/invalid` will trigger the `notFound` -middleware. Contrary to generic error pages this handler cannot be nested. +[middleware](/docs/concepts/middleware). Contrary to generic error pages this +handler cannot be nested. ## Throwing HTTP errors @@ -116,4 +117,4 @@ app.onError("*", (ctx) => { ``` `HttpError` is also available in the browser via `fresh/runtime` for use in -island code. +[island code](/docs/concepts/islands). diff --git a/docs/latest/advanced/head.md b/docs/latest/advanced/head.md index e6bdd4b6309..aa4ab667e88 100644 --- a/docs/latest/advanced/head.md +++ b/docs/latest/advanced/head.md @@ -12,12 +12,13 @@ is a crucial element in HTML to set metadata for a page. It allows you to: - Include JavaScript code with `<script>` > [info]: The outer HTML structure including `<head>` is typically created -> inside `_app.tsx`. +> inside [`_app.tsx`](/docs/concepts/app). ## Passing metadata from `ctx.state` -For simple scenarios passing metadata along from a handler or a middleware by -writing to `ctx.state` is often sufficient. +For simple scenarios passing metadata along from a handler or a +[middleware](/docs/concepts/middleware) by writing to `ctx.state` is often +sufficient. ```tsx routes/_app.tsx import { define } from "../util.ts"; @@ -39,8 +40,8 @@ export default define.page((ctx) => { ## Using the `<Head>`-component -For more complex scenarios, or to set page metadata from islands, Fresh ships -with the `<Head>`-component. +For more complex scenarios, or to set page metadata from +[islands](/docs/concepts/islands), Fresh ships with the `<Head>`-component. > [info]: The `<Head>` component is not dynamic by default. It will not > automatically update the document title or other head elements on the client diff --git a/docs/latest/advanced/layouts.md b/docs/latest/advanced/layouts.md index 8042e2db15a..ab194b9c160 100644 --- a/docs/latest/advanced/layouts.md +++ b/docs/latest/advanced/layouts.md @@ -39,7 +39,7 @@ If you browse to the `/` route, Fresh will render the following HTML ## Multiple layouts You can register multiple layouts for different paths. Layouts are inherited -from parent paths — a layout at `"*"` applies to all routes, and more specific +from parent paths - a layout at `"*"` applies to all routes, and more specific layouts are added on top: ```ts main.ts @@ -67,7 +67,7 @@ const app = new App() ## Options -Ignore the app wrapper component: +Ignore the [app wrapper](/docs/concepts/app) component: ```ts main.ts app.layout("/foo/bar", MyComponent, { skipAppWrapper: true }); diff --git a/docs/latest/advanced/opentelemetry.md b/docs/latest/advanced/opentelemetry.md index 846e0c72562..01f897515d5 100644 --- a/docs/latest/advanced/opentelemetry.md +++ b/docs/latest/advanced/opentelemetry.md @@ -11,16 +11,17 @@ requests flow through your application. Fresh creates spans for: -- **Middleware execution** — each middleware in the chain -- **Route handler execution** — handler function calls -- **Rendering** — server-side page rendering -- **Static file serving** — file lookups and responses -- **Lazy route loading** — dynamic imports of route modules +- **[Middleware](/docs/concepts/middleware) execution** - each middleware in the + chain +- **Route handler execution** - handler function calls +- **Rendering** - server-side page rendering +- **Static file serving** - file lookups and responses +- **Lazy route loading** - dynamic imports of route modules ## Enabling tracing Fresh uses the `@opentelemetry/api` package (the vendor-neutral API). Spans are -created automatically — you just need to provide an OpenTelemetry SDK and +created automatically - you just need to provide an OpenTelemetry SDK and exporter to collect them. If no exporter is configured, the spans are silently discarded (no performance @@ -41,5 +42,5 @@ This exports traces to an OTLP-compatible collector (configure the endpoint with ### With Deno Deploy -[Deno Deploy](https://deno.com/deploy) collects Fresh traces automatically when -using the Fresh preset — no configuration needed. +[Deno Deploy](/docs/deployment/deno-deploy) collects Fresh traces automatically +when using the Fresh preset - no configuration needed. diff --git a/docs/latest/advanced/serialization.md b/docs/latest/advanced/serialization.md index d74f8aeff2e..def92861093 100644 --- a/docs/latest/advanced/serialization.md +++ b/docs/latest/advanced/serialization.md @@ -3,9 +3,10 @@ description: | What types can be passed as island props, how Fresh serializes data between server and client, and common pitfalls. --- -When Fresh renders a page on the server, island props must be serialized to JSON -and sent to the browser for hydration. Fresh uses a custom serialization system -that supports more types than standard `JSON.stringify`. +When Fresh renders a page on the server, [island](/docs/concepts/islands) props +must be serialized to JSON and sent to the browser for hydration. Fresh uses a +custom serialization system that supports more types than standard +`JSON.stringify`. ## Supported types @@ -25,7 +26,7 @@ The following types can be passed as island props: | `Set` | Values must be serializable | | `Map` | Keys and values must be serializable | | `Uint8Array` | Binary data | -| `Signal` | From `@preact/signals` — see [Signals](/docs/concepts/signals) | +| `Signal` | From `@preact/signals` - see [Signals](/docs/concepts/signals) | | `Computed Signal` | Read-only signals | | JSX Elements | Server-rendered JSX passed to islands | @@ -33,17 +34,17 @@ The following types can be passed as island props: The following **cannot** be passed as island props: -- **Functions and closures** — there is no way to transfer executable code -- **Class instances** — only plain objects are supported (no custom prototypes) -- **Symbols** — not representable in JSON -- **WeakMap / WeakSet** — cannot be enumerated -- **Streams, Promises** — async values cannot be frozen for transfer +- **Functions and closures** - there is no way to transfer executable code +- **Class instances** - only plain objects are supported (no custom prototypes) +- **Symbols** - not representable in JSON +- **WeakMap / WeakSet** - cannot be enumerated +- **Streams, Promises** - async values cannot be frozen for transfer ```tsx -// WRONG — functions cannot be serialized +// WRONG - functions cannot be serialized <MyIsland onClick={() => console.log("clicked")} /> -// WRONG — class instance loses its prototype +// WRONG - class instance loses its prototype <MyIsland data={new MyCustomClass()} /> ``` @@ -70,7 +71,7 @@ When a `Signal` is detected in island props: reactive signal If the same signal object is passed to multiple islands, it is serialized once -and all islands receive the same signal instance on the client — keeping them +and all islands receive the same signal instance on the client - keeping them synchronized. Computed signals are serialized by reading their current value and wrapping it @@ -96,5 +97,5 @@ runtime error during serialization. Keep island props to plain data: ### Large props Every byte of serialized props is embedded in the HTML and parsed on the client. -Keep island props small — pass IDs or minimal data, and fetch the rest +Keep island props small - pass IDs or minimal data, and fetch the rest client-side if needed. diff --git a/docs/latest/advanced/troubleshooting.md b/docs/latest/advanced/troubleshooting.md index 56058b316c0..72980c62357 100644 --- a/docs/latest/advanced/troubleshooting.md +++ b/docs/latest/advanced/troubleshooting.md @@ -41,7 +41,7 @@ already been resolved in the latest version of Fresh. Fresh 1.x heavily relied on [esm.sh](https://esm.sh/) to be able to use npm packages with Fresh. This continued a bit through the early alpha versions of -Fresh 2. With the move to [`vite`](https://vite.dev/) this is not necessary +Fresh 2. With the move to [`vite`](/docs/advanced/vite) this is not necessary anymore and you should use the relevant npm package directly from npm. ```diff deno.json diff --git a/docs/latest/advanced/vite.md b/docs/latest/advanced/vite.md index e3abd987cc5..646e7cf3c08 100644 --- a/docs/latest/advanced/vite.md +++ b/docs/latest/advanced/vite.md @@ -5,7 +5,8 @@ description: | Fresh 2 uses [Vite](https://vite.dev/) for development and production builds. The Fresh Vite plugin handles JSX configuration, Hot Module Replacement (HMR), -island discovery, client/server code splitting, and React-to-Preact aliasing. +[island](/docs/concepts/islands) discovery, client/server code splitting, and +React-to-Preact aliasing. ## Configuration diff --git a/docs/latest/concepts/app.md b/docs/latest/concepts/app.md index 5cb4864f791..271c3b9b9f7 100644 --- a/docs/latest/concepts/app.md +++ b/docs/latest/concepts/app.md @@ -4,8 +4,8 @@ description: | --- The `App` class is the heart of Fresh and routes incoming requests to the -correct middlewares. This is where routes, middlewares, layouts and more are -defined. +correct [middlewares](/docs/concepts/middleware). This is where routes, +middlewares, [layouts](/docs/concepts/layouts) and more are defined. ```ts main.ts const app = new App() @@ -259,8 +259,8 @@ app.all("/api/foo", async () => { ## `.fsRoute()` -Injects all file-based routes, middlewares, layouts and error pages to the app -instance. +Injects all [file-based routes](/docs/concepts/file-routing), middlewares, +layouts and [error pages](/docs/advanced/error-handling) to the app instance. ```ts app.fsRoutes(); @@ -397,7 +397,7 @@ app.listen({ port: 4000 }); > **Important:** `.listen()` is only used when running your app directly with > `deno run -A main.ts`. The default project setup uses `deno task dev` (Vite > dev server) and `deno task start` (`deno serve`), which spawn their own -> servers — calling `.listen()` alongside these will create a second server and +> servers - calling `.listen()` alongside these will create a second server and > cause `AddrInUse` errors. > > To customize the port in the default setup: diff --git a/docs/latest/concepts/architecture.md b/docs/latest/concepts/architecture.md index 66c72600a09..c3f7ccc3f38 100644 --- a/docs/latest/concepts/architecture.md +++ b/docs/latest/concepts/architecture.md @@ -4,8 +4,8 @@ description: | --- Fresh is a server-first web framework. Pages are rendered on the server and only -the interactive parts (islands) ship JavaScript to the browser. This page -explains how a request flows through the framework. +the interactive parts ([islands](/docs/concepts/islands)) ship JavaScript to the +browser. This page explains how a request flows through the framework. ## Request lifecycle @@ -50,7 +50,7 @@ explains how a request flows through the framework. <!-- Scoped Middleware --> <div class="flow-step"> <div class="flow-box"><strong>Scoped Middleware</strong></div> - <div class="flow-desc">Path‑specific middleware</div> + <div class="flow-desc">Middleware that only runs for matching path prefixes (e.g. /admin/*). Used for auth, logging, or other route‑specific logic.</div> </div> <div class="flow-arrow">↓</div> <!-- Handler --> @@ -83,7 +83,7 @@ explains how a request flows through the framework. <!-- HTML Response --> <div class="flow-step"> <div class="flow-box accent"><strong>HTML + JS Response</strong><small>sent to browser</small></div> - <div class="flow-desc">Server‑rendered HTML with island props serialized inline</div> + <div class="flow-desc">Server‑rendered HTML with island props <a href="/docs/advanced/serialization">serialized</a> inline</div> </div> <!-- Client section --> <div class="flow-divider">Client (Browser)</div> @@ -102,7 +102,7 @@ explains how a request flows through the framework. Every page is fully rendered to HTML on the server before being sent to the browser. This means: -- Pages are visible immediately — no blank loading screens +- Pages are visible immediately - no blank loading screens - Search engines see complete content - Pages work without JavaScript enabled @@ -110,11 +110,12 @@ browser. This means: Fresh uses the [islands architecture](https://jasonformat.com/islands-architecture/). Only -components in the `islands/` directory are hydrated in the browser. Everything -else is static HTML that never runs JavaScript on the client. +components in the `islands/` directory are [hydrated](/docs/concepts/islands) in +the browser. Everything else is static HTML that never runs JavaScript on the +client. This means a page with a single interactive button only ships the JavaScript for -that button — not for the entire page. +that button - not for the entire page. ### Middleware chain @@ -136,17 +137,33 @@ app.use(async (ctx) => { }); ``` +Scoped middleware runs only for requests that match a specific path prefix. Pass +a path pattern as the first argument to `app.use()`: + +```ts +app.use("/admin/*", async (ctx) => { + // Only runs for /admin/* routes + const user = ctx.state.user; + if (!user?.isAdmin) return new Response("Forbidden", { status: 403 }); + return ctx.next(); +}); +``` + +Global middleware runs on every request; scoped middleware lets you apply logic +like authentication or logging to a subset of routes. + ### Layout inheritance -Layouts wrap page components and are inherited from parent directories. A page -at `routes/blog/post.tsx` inherits layouts from: +[Layouts](/docs/concepts/layouts) wrap page components and are inherited from +parent directories. A page at `routes/blog/post.tsx` inherits layouts from: 1. `routes/_layout.tsx` (root layout) 2. `routes/blog/_layout.tsx` (section layout) Layouts nest from the outside in: the root layout is outermost, each deeper layout wraps closer to the page, and the innermost layout directly wraps the -page component. The app wrapper (`_app.tsx`) wraps everything. +page component. The [app wrapper](/docs/concepts/app) (`_app.tsx`) wraps +everything. ### Build and deploy diff --git a/docs/latest/concepts/context.md b/docs/latest/concepts/context.md index 726d08e78f4..e38d8689945 100644 --- a/docs/latest/concepts/context.md +++ b/docs/latest/concepts/context.md @@ -2,8 +2,9 @@ description: The Context object is shared across all middlewares and provides access to the request, URL, params, state, and response helpers. --- -The `Context` instance is shared across all middlewares in Fresh. Use it to -respond with HTML, trigger redirects, access the incoming +The `Context` instance is shared across all +[middlewares](/docs/concepts/middleware) in Fresh. Use it to respond with HTML, +trigger redirects, access the incoming [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) or read other metadata. @@ -98,7 +99,8 @@ app.use((ctx) => { ## `.error` If an error was thrown, this property will hold the caught value (default: -`null`). This is typically used mainly on an error page. +`null`). This is typically used mainly on an +[error page](/docs/advanced/error-handling). ```ts app.onError((ctx) => { diff --git a/docs/latest/concepts/data-fetching.md b/docs/latest/concepts/data-fetching.md index cf49abeeaea..97bb9e9c1ca 100644 --- a/docs/latest/concepts/data-fetching.md +++ b/docs/latest/concepts/data-fetching.md @@ -83,8 +83,8 @@ between handler and component. ## Passing state from middleware -Middleware can set values on `ctx.state` that are available to all downstream -handlers and components: +[Middleware](/docs/concepts/middleware) can set values on `ctx.state` that are +available to all downstream handlers and components: ```ts routes/_middleware.ts import { define } from "@/utils.ts"; diff --git a/docs/latest/concepts/file-routing.md b/docs/latest/concepts/file-routing.md index 18f2f7ec8b0..236261b6a94 100644 --- a/docs/latest/concepts/file-routing.md +++ b/docs/latest/concepts/file-routing.md @@ -19,8 +19,8 @@ const app = new App({ basePath: "/foo" }) ``` > [info]: The `staticFiles()` middleware is required when using file based -> routing. Otherwise the necessary JavaScript files for islands won't be served -> to the browser. +> routing. Otherwise the necessary JavaScript files for +> [islands](/docs/concepts/islands) won't be served to the browser. Example project structure: @@ -48,11 +48,11 @@ Example project structure: **Special directories inside `routes/`:** -- **`(_islands)`** — Files in this directory are treated as +- **`(_islands)`** - Files in this directory are treated as [islands](/docs/concepts/islands), just like files in the top-level `islands/` folder. This lets you co-locate islands next to the routes that use them. -- **`(_components)`** — A conventional directory for non-island components that - are only used by nearby routes. Fresh does not treat these files specially — +- **`(_components)`** - A conventional directory for non-island components that + are only used by nearby routes. Fresh does not treat these files specially - the parentheses just prevent them from becoming routes. File names are mapped to route patterns as follows: diff --git a/docs/latest/concepts/islands.md b/docs/latest/concepts/islands.md index 021e3e36837..9f0fa66e43e 100644 --- a/docs/latest/concepts/islands.md +++ b/docs/latest/concepts/islands.md @@ -39,8 +39,9 @@ const app = new App() ## Passing props to islands -Passing props to islands is supported, but only if the props are serializable. -Fresh can serialize the following types of values: +Passing props to islands is supported, but only if the props are +[serializable](/docs/advanced/serialization). Fresh can serialize the following +types of values: - Primitive types `string`, `number`, `boolean`, `bigint`, `undefined`, and `null` @@ -53,7 +54,7 @@ Fresh can serialize the following types of values: - Collections `Map` and `Set` - Plain objects with string keys and serializable values - Arrays containing serializable values -- Preact Signals (if the inner value is serializable) +- Preact [Signals](/docs/concepts/signals) (if the inner value is serializable) Circular references are supported. If an object or signal is referenced multiple times, it is only serialized once and the references are restored upon @@ -174,7 +175,7 @@ export function MyElement() { ### Using third-party web components -Third-party web component libraries work the same way — import and register them +Third-party web component libraries work the same way - import and register them inside an island: ```tsx islands/ThirdPartyElement.tsx diff --git a/docs/latest/concepts/layouts.md b/docs/latest/concepts/layouts.md index 1a53a128f0d..bae5f6f6fea 100644 --- a/docs/latest/concepts/layouts.md +++ b/docs/latest/concepts/layouts.md @@ -27,7 +27,7 @@ Preact component. Only one such layout is allowed per sub directory. The component to be wrapped is received via props, in addition to a few other things. This allows for the introduction of a global container functioning as a template which can be conditioned based on state and params. Note that any state -set by middleware is available via `props.state`. +set by [middleware](/docs/concepts/middleware) is available via `props.state`. ```tsx routes/sub/_layout.tsx import { define } from "../../utils.ts"; diff --git a/docs/latest/concepts/middleware.md b/docs/latest/concepts/middleware.md index fa65d75cd4a..2c4378269ec 100644 --- a/docs/latest/concepts/middleware.md +++ b/docs/latest/concepts/middleware.md @@ -58,8 +58,9 @@ Fresh ships with the following middlewares built-in: ## Filesystem-based middlewares -With file system based routing you can define a middleware in a `_middleware.ts` -file inside the `routes/` folder or any of its subfolders. +With [file system based routing](/docs/concepts/file-routing) you can define a +middleware in a `_middleware.ts` file inside the `routes/` folder or any of its +subfolders. ```ts routes/_middleware.ts import { define } from "../utils.ts"; diff --git a/docs/latest/concepts/routing.md b/docs/latest/concepts/routing.md index 961507b519c..74571110c90 100644 --- a/docs/latest/concepts/routing.md +++ b/docs/latest/concepts/routing.md @@ -46,7 +46,7 @@ This means the registration order matters for dynamic routes: const app = new App() // This is checked first since it's registered first .get("/posts/featured", () => new Response("Featured posts")) - // This is checked second — won't match "/posts/featured" because it's + // This is checked second - won't match "/posts/featured" because it's // already handled above .get("/posts/:id", (ctx) => new Response(`Post: ${ctx.params.id}`)); ``` diff --git a/docs/latest/concepts/signals.md b/docs/latest/concepts/signals.md index 1e8b6b4680e..9d55afec180 100644 --- a/docs/latest/concepts/signals.md +++ b/docs/latest/concepts/signals.md @@ -4,9 +4,9 @@ description: | --- [Signals](https://preactjs.com/guide/v10/signals/) are Preact's reactive -primitive for managing state in islands. When a signal's value changes, any -component that reads it re-renders automatically — no need for `setState` or -manual subscriptions. +primitive for managing state in [islands](/docs/concepts/islands). When a +signal's value changes, any component that reads it re-renders automatically - +no need for `setState` or manual subscriptions. ## Creating signals @@ -78,7 +78,7 @@ export default function Home() { } ``` -Both sliders share the same signal — moving one updates the other. When the same +Both sliders share the same signal - moving one updates the other. When the same signal object is passed to multiple islands, Fresh preserves the reference so they stay synchronized. @@ -126,8 +126,8 @@ for more patterns. ## Serialization -When signals are passed as island props, Fresh handles serialization -automatically: +When signals are passed as island props, Fresh handles +[serialization](/docs/advanced/serialization) automatically: - The signal's current value is extracted on the server via `.peek()` - On the client, the value is wrapped back into a live `signal()` or @@ -135,5 +135,5 @@ automatically: - Circular references and duplicate signal references are preserved The signal's inner value must itself be serializable (see -[Islands — Passing props](/docs/concepts/islands#passing-props-to-islands) for +[Islands - Passing props](/docs/concepts/islands#passing-props-to-islands) for the full list of supported types). diff --git a/docs/latest/concepts/static-files.md b/docs/latest/concepts/static-files.md index 57a5e4caa98..45f31a9a440 100644 --- a/docs/latest/concepts/static-files.md +++ b/docs/latest/concepts/static-files.md @@ -18,9 +18,9 @@ const app = new App() ## Imported assets vs static files -When using Fresh with Vite (now the default), **files that you import in your -JavaScript/TypeScript code should not be placed in the `static/` folder**. This -prevents file duplication during the build process. +When using Fresh with [Vite](/docs/advanced/vite) (now the default), **files +that you import in your JavaScript/TypeScript code should not be placed in the +`static/` folder**. This prevents file duplication during the build process. ```tsx // Don't import from static/ diff --git a/docs/latest/contributing/index.md b/docs/latest/contributing/index.md index 404a4272050..6918695333f 100644 --- a/docs/latest/contributing/index.md +++ b/docs/latest/contributing/index.md @@ -90,13 +90,13 @@ convention, and require the `-A` flag. Snapshot tests are stored in `__snapshots__/` directories. Some tests may fail locally but pass in CI (`Could not find server address`, -`Text file busy (os error 26)`) — these can be safely ignored. +`Text file busy (os error 26)`) - these can be safely ignored. ## Pull Requests - Run `deno task ok` before submitting to catch formatting, lint, and type errors early -- Keep PRs focused — one feature or fix per PR +- Keep PRs focused - one feature or fix per PR - Add or update tests for any behavior changes -- Follow existing code style — the repository uses `deno fmt` for formatting and +- Follow existing code style - the repository uses `deno fmt` for formatting and `deno lint` for linting diff --git a/docs/latest/deployment/cloudflare-workers.md b/docs/latest/deployment/cloudflare-workers.md index de38accb487..0c191cf0f3f 100644 --- a/docs/latest/deployment/cloudflare-workers.md +++ b/docs/latest/deployment/cloudflare-workers.md @@ -5,7 +5,8 @@ description: "Deploy Fresh on Cloudflare Workers" Deploy Fresh to Cloudflare Workers by following these instructions: 1. Run `deno install --allow-scripts npm:@cloudflare/vite-plugin npm:wrangler` -2. Add the cloudflare plugin in your vite configuration file: +2. Add the cloudflare plugin in your [vite](/docs/advanced/vite) configuration + file: ```diff vite.config.ts import { defineConfig } from "vite"; diff --git a/docs/latest/deployment/deno-deploy.md b/docs/latest/deployment/deno-deploy.md index f48fc85dacd..638b68aad9a 100644 --- a/docs/latest/deployment/deno-deploy.md +++ b/docs/latest/deployment/deno-deploy.md @@ -5,7 +5,7 @@ description: "Deploy Fresh on Deno Deploy" The recommended way to deploy Fresh is by using [Deno Deploy](https://deno.com/deploy). It will automatically create branch previews for pull requests, collect request and HTTP metrics, as well as collect -traces for you out of the box. +[traces](/docs/advanced/opentelemetry) for you out of the box. ## Setup @@ -37,8 +37,8 @@ You can set environment variables in the Deno Deploy dashboard under your project's **Settings > Environment Variables** section. These are available at runtime via `Deno.env.get()`. -For variables that need to be available in island code (client-side), prefix -them with `FRESH_PUBLIC_` — see +For variables that need to be available in [island](/docs/concepts/islands) code +(client-side), prefix them with `FRESH_PUBLIC_` - see [Environment Variables](/docs/advanced/environment-variables). ## Custom domains @@ -53,7 +53,7 @@ If your deployment fails to start: 1. Ensure `deno task build` has been run (check that the Fresh preset is selected) -2. Verify your entry point is `_fresh/server.js`, not `main.ts` — Fresh 2 +2. Verify your entry point is `_fresh/server.js`, not `main.ts` - Fresh 2 generates the server entry during the build step 3. Check the deployment logs in the Deno Deploy dashboard for specific errors diff --git a/docs/latest/deployment/docker.md b/docs/latest/deployment/docker.md index 8dacda23b97..64b5498f88a 100644 --- a/docs/latest/deployment/docker.md +++ b/docs/latest/deployment/docker.md @@ -6,12 +6,14 @@ You can deploy Fresh to any platform that can run Docker containers. Docker is a tool to containerize projects and portably run them on any supported platform. When packaging your Fresh app for Docker, it is important that you set the -`DENO_DEPLOYMENT_ID` environment variable in your container. This variable needs -to be set to an opaque string ID that represents the version of your application -that is currently being run. This could be a Git commit hash, or a hash of all -files in your project. It is critical for the function of Fresh that this ID -changes when _any_ file in your project changes - if it doesn't, incorrect -caching **will** cause your project to not function correctly. +`DENO_DEPLOYMENT_ID` +[environment variable](/docs/advanced/environment-variables) in your container. +This variable needs to be set to an opaque string ID that represents the version +of your application that is currently being run. This could be a Git commit +hash, or a hash of all files in your project. It is critical for the function of +Fresh that this ID changes when _any_ file in your project changes - if it +doesn't, incorrect caching **will** cause your project to not function +correctly. Here is an example `Dockerfile` for a Fresh project: diff --git a/docs/latest/deployment/index.md b/docs/latest/deployment/index.md index 5043586f31a..7c8352a8908 100644 --- a/docs/latest/deployment/index.md +++ b/docs/latest/deployment/index.md @@ -16,8 +16,8 @@ Then run the build: deno task build ``` -> [info]: This runs `vite build` under the hood. If you're migrating from Fresh -> 1.x and still have a `dev.ts` file, see the +> [info]: This runs [vite](/docs/advanced/vite) build under the hood. If you're +> migrating from Fresh 1.x and still have a `dev.ts` file, see the > [migration guide](/docs/examples/migration-guide) for updating your tasks. Once completed, it will have created a `_fresh` folder in the project directory diff --git a/docs/latest/examples/api-routes.md b/docs/latest/examples/api-routes.md index aaa770b1837..8b62cfe47f9 100644 --- a/docs/latest/examples/api-routes.md +++ b/docs/latest/examples/api-routes.md @@ -4,7 +4,7 @@ description: | --- A route that exports only `handlers` (no default component export) becomes an -API endpoint — it returns responses directly instead of rendering HTML. +API endpoint - it returns responses directly instead of rendering HTML. ## Basic JSON API @@ -69,7 +69,8 @@ export const handlers = define.handlers((ctx) => { ## Programmatic API routes -API routes can also be defined directly on the app without file-based routing: +API routes can also be defined directly on the app without +[file-based routing](/docs/concepts/file-routing): ```ts main.ts const app = new App() diff --git a/docs/latest/examples/common-patterns.md b/docs/latest/examples/common-patterns.md index c60dba1e209..0fe5d1e28b9 100644 --- a/docs/latest/examples/common-patterns.md +++ b/docs/latest/examples/common-patterns.md @@ -7,7 +7,8 @@ This page collects common patterns you'll encounter when building Fresh apps. ## Protected routes -Use middleware to check authentication and redirect unauthenticated users: +Use [middleware](/docs/concepts/middleware) to check authentication and redirect +unauthenticated users: ```ts routes/dashboard/_middleware.ts import { define } from "@/utils.ts"; diff --git a/docs/latest/examples/migration-guide.md b/docs/latest/examples/migration-guide.md index ff5aa64d4f0..5effb7bc13f 100644 --- a/docs/latest/examples/migration-guide.md +++ b/docs/latest/examples/migration-guide.md @@ -98,7 +98,8 @@ export const app = new App() ## Merging error pages Both the `_500.tsx` and `_404.tsx` template have been unified into a single -`_error.tsx` template. +`_error.tsx` template. See [error pages](/docs/advanced/error-handling) for +details. ```diff Project structure └── <root>/routes/ @@ -203,9 +204,9 @@ have a trailing slash at the end or that they will never have one. ### Unified middleware signatures -Middleware, handler and route component signatures have been unified to all look -the same. Instead of receiving two arguments, they receive one. The `Request` -object is stored on the context object as `ctx.req`. +[Middleware](/docs/concepts/middleware), handler and route component signatures +have been unified to all look the same. Instead of receiving two arguments, they +receive one. The `Request` object is stored on the context object as `ctx.req`. ```diff middleware.ts - const middleware = (req, ctx) => new Response("ok"); diff --git a/docs/latest/examples/rendering-raw-html.md b/docs/latest/examples/rendering-raw-html.md index fbd107bc1f0..86f8a5c0eb3 100644 --- a/docs/latest/examples/rendering-raw-html.md +++ b/docs/latest/examples/rendering-raw-html.md @@ -4,8 +4,8 @@ description: | --- Text content in Fresh is always escaped, whether serverside rendered or rendered -in islands. While this is generally desired, it can create issues in certain -situations. +in [islands](/docs/concepts/islands). While this is generally desired, it can +create issues in certain situations. To address this you can render raw HTML via Preact's `dangerouslySetInnerHTML` prop: diff --git a/docs/latest/examples/sharing-state-between-islands.md b/docs/latest/examples/sharing-state-between-islands.md index b1b8ff746a2..e3f9b17318f 100644 --- a/docs/latest/examples/sharing-state-between-islands.md +++ b/docs/latest/examples/sharing-state-between-islands.md @@ -189,8 +189,8 @@ export default define.page(function CartPage() { ``` The `cart` signal is created per-render (not at module level), so each request -gets its own independent cart. Fresh serializes the signal and passes it to both -islands, keeping them in sync on the client. +gets its own independent cart. Fresh [serializes](/docs/advanced/serialization) +the signal and passes it to both islands, keeping them in sync on the client. > [!CAUTION] > Avoid creating signals at the module level (e.g. diff --git a/docs/latest/getting-started/index.md b/docs/latest/getting-started/index.md index 1abd5a99992..c8c30623391 100644 --- a/docs/latest/getting-started/index.md +++ b/docs/latest/getting-started/index.md @@ -22,10 +22,10 @@ project folder should look like this: │ └── Button.tsx ├── islands/ # Components that need JS to run client-side │ └── Counter.tsx -├── routes/ # File system based routes +├── routes/ # [File system based routes](/docs/concepts/file-routing) │   ├── api/ │   │ └── [name].tsx # API route for /api/:name -│   ├── _app.tsx # Renders the outer <html> content structure +│   ├── [_app.tsx](/docs/concepts/app) # Renders the outer <html> content structure │   └── index.tsx # Renders / ├── static/ # Contains static assets like css, logos, etc │   └── ... @@ -33,7 +33,7 @@ project folder should look like this: ├── client.ts # Client entry file that's loaded on every page. ├── main.ts # The server entry file of your app ├── deno.json # Contains dependencies, tasks, etc -└── vite.config.ts # Vite configuration file +└── [vite.config.ts](/docs/advanced/vite) # Vite configuration file ``` ## Path aliases diff --git a/docs/latest/introduction/index.md b/docs/latest/introduction/index.md index 0a1d77795f9..bbd5a7237e9 100644 --- a/docs/latest/introduction/index.md +++ b/docs/latest/introduction/index.md @@ -9,20 +9,16 @@ Fresh is a small, fast and extensible full stack web framework built on Web Standards. It's designed for building high-quality, performant, and personalized web applications. -```ts main.ts +```tsx main.tsx import { App } from "fresh"; const app = new App() - .get("/", () => new Response("hello world")); + .get("/", () => new Response("hello world")) + .get("/jsx", (ctx) => ctx.render(<h1>render JSX!</h1>)); app.listen(); ``` -## Requirements - -- [Deno](https://deno.com/) v2.1 or later -- TypeScript is supported out of the box (no configuration needed) - ## Quick Start Create a new Fresh app by running: @@ -43,7 +39,8 @@ referred to as the - **Lightweight** 🏎️ - Only ship the JavaScript you need - **Extensible** 🧩 - Nearly every aspect can be customized - **Powerful & small API** 🤗 - Familiar APIs make you productive quickly -- **Built-in OpenTelemetry** 📈 - Built-in support for OpenTelemetry +- **Built-in [OpenTelemetry](/docs/advanced/opentelemetry)** 📈 - Built-in + support for OpenTelemetry ## When to use Fresh @@ -56,6 +53,12 @@ home page, an e-commerce shop or something like GitHub or Bluesky. - Landing pages & Documentation - CRUD apps +Fresh's small API surface and +[file-based conventions](/docs/concepts/file-routing) also make it a great fit +for AI-assisted development. Agents can scaffold routes, add +[middleware](/docs/concepts/middleware), and build features with minimal context +because the framework is simple and predictable. + That said, if you want to build a Single-Page-App (=SPA), then Fresh is not the right framework. @@ -65,14 +68,11 @@ Fresh powers [deno.com](https://deno.com) and [Deno Deploy][deno-deploy] among many other projects at Deno. It's also used by [deco.cx](https://deco.cx/) for e-commerce projects. -## Where to host Fresh apps? - -Fresh is most often deployed on [Deno Deploy][deno-deploy] where it can be -deployed with 1-click and has out of the box integrations with metrics among -other things. +## Hosting -Fresh projects can be deployed manually to any platform with [Deno][deno] like -via docker containers too. +Fresh runs anywhere [Deno][deno] runs. Deploy with a single click on +[Deno Deploy][deno-deploy], or package your app in a Docker container for any +cloud provider or self-hosted infrastructure. [preact]: https://preactjs.com [deno]: https://deno.com diff --git a/docs/latest/plugins/index.md b/docs/latest/plugins/index.md index 336ed8edc48..ea64d1b2a65 100644 --- a/docs/latest/plugins/index.md +++ b/docs/latest/plugins/index.md @@ -9,8 +9,8 @@ features in Fresh itself are built using these APIs. ## Custom middlewares If you need to modify requests, add HTTP headers or pass additional data to -other middlewares via `ctx.state`, then going with a middleware is the way to -go. +other [middlewares](/docs/concepts/middleware) via `ctx.state`, then going with +a middleware is the way to go. ```ts middleware/fresh.ts const addXFreshHeader = define.middleware(async (ctx) => { diff --git a/tools/check_links.ts b/tools/check_links.ts index 7665bbfa692..7c0e9a48c5d 100644 --- a/tools/check_links.ts +++ b/tools/check_links.ts @@ -165,7 +165,7 @@ async function crawlPage(pageUrl: URL, referrer: string) { const urlStr = nextUrl.href; if (nextUrl.origin === rootUrl.origin) { - // Internal link — crawl the page if it's a docs page + // Internal link -- crawl the page if it's a docs page if ( !visitedPages.has(nextUrl.pathname) && nextUrl.pathname.startsWith("/docs") @@ -178,7 +178,7 @@ async function crawlPage(pageUrl: URL, referrer: string) { } } } else { - // External link — verify it's live + // External link -- verify it's live if (!checkedUrls.has(urlStr)) { linkChecks.push(checkUrl(urlStr, pathname).then(() => {})); } diff --git a/www/components/homepage/PartialsSection.tsx b/www/components/homepage/PartialsSection.tsx index 0f7432175ac..0bbd977e531 100644 --- a/www/components/homepage/PartialsSection.tsx +++ b/www/components/homepage/PartialsSection.tsx @@ -67,7 +67,7 @@ export function PartialsSection() { <SectionHeading>Stream HTML straight from the server</SectionHeading> <p> Fresh Partials let you fetch HTML and slot it directly into the - page, without a full page reload—perfect for interactive elements + page, without a full page reload - perfect for interactive elements and dynamic apps. </p> <FancyLink href="/docs/advanced/partials" class="mt-4"> From 4af0d2c91adb646ef73f571a3a17ab28adf8211d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sun, 29 Mar 2026 09:57:59 +0200 Subject: [PATCH 56/59] docs: update init command to use deno create Replace `deno run -Ar jsr:@fresh/init` with `deno create @fresh/init` across docs, READMEs, homepage, and init help text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- AGENTS.md | 2 +- README.md | 2 +- docs/latest/examples/migration-guide.md | 2 +- docs/latest/getting-started/index.md | 2 +- docs/latest/introduction/index.md | 2 +- packages/fresh/README.md | 2 +- packages/init/README.md | 2 +- packages/init/src/init.ts | 6 +++--- www/components/homepage/Hero.tsx | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d718f91ce62..a88208cc9a2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ with workspace members in `packages/*` and `www/`. - **`packages/plugin-vite/`** (`@fresh/plugin-vite`): Vite integration plugin with dev server, SSR/client builds, and HMR. - **`packages/init/`** (`@fresh/init`): Project scaffolding - (`deno run -A jsr:@fresh/init`). + (`deno create @fresh/init`). - **`packages/update/`** (`@fresh/update`): Automated Fresh 1.x to 2.x migration tool using ts-morph for AST transforms. - **`packages/build-id/`** (`@fresh/build-id`): Build/deployment ID generation. diff --git a/README.md b/README.md index ecee756c165..9921eea303f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ You can scaffold a new project by running the Fresh init script. To scaffold a project run the following: ```sh -deno run -Ar jsr:@fresh/init +deno create @fresh/init ``` Then navigate to the newly created project folder: diff --git a/docs/latest/examples/migration-guide.md b/docs/latest/examples/migration-guide.md index 5effb7bc13f..954d7bf065f 100644 --- a/docs/latest/examples/migration-guide.md +++ b/docs/latest/examples/migration-guide.md @@ -314,7 +314,7 @@ to [update the command](/docs/deployment/deno-compile) to generate the binary. If you run into problems with upgrading your app, first, try starting a new Fresh 2 project and looking at the new structure. -eg. `mkdir fresh2-demo && cd fresh2-demo && deno run -Ar jsr:@fresh/init` +eg. `mkdir fresh2-demo && cd fresh2-demo && deno create @fresh/init` ### 2. Document diff --git a/docs/latest/getting-started/index.md b/docs/latest/getting-started/index.md index c8c30623391..9122bd72134 100644 --- a/docs/latest/getting-started/index.md +++ b/docs/latest/getting-started/index.md @@ -9,7 +9,7 @@ Let's set up your first Fresh project. To create a new project, run this command: ```sh Terminal -deno run -Ar jsr:@fresh/init +deno create @fresh/init ``` This will spawn a short wizard that guides you through the setup, like the diff --git a/docs/latest/introduction/index.md b/docs/latest/introduction/index.md index bbd5a7237e9..0e833398320 100644 --- a/docs/latest/introduction/index.md +++ b/docs/latest/introduction/index.md @@ -24,7 +24,7 @@ app.listen(); Create a new Fresh app by running: ```sh Terminal -deno run -Ar jsr:@fresh/init +deno create @fresh/init ``` ## Features diff --git a/packages/fresh/README.md b/packages/fresh/README.md index 2ac197c1162..6bdd05afc45 100644 --- a/packages/fresh/README.md +++ b/packages/fresh/README.md @@ -11,7 +11,7 @@ web applications. Generate a new Fresh project with `@fresh/init`: ```sh -deno run -Ar jsr:@fresh/init +deno create @fresh/init ``` Add middleware, routes, & endpoints as needed via the `routes/` folder or diff --git a/packages/init/README.md b/packages/init/README.md index eec288dd023..54198fda9fc 100644 --- a/packages/init/README.md +++ b/packages/init/README.md @@ -3,7 +3,7 @@ This is a CLI tool to bootstrap a new Fresh project. To do so, run this command: ```sh -deno run -Ar jsr:@fresh/init +deno create @fresh/init ``` Go to [https://fresh.deno.dev/](https://fresh.deno.dev/) for more information diff --git a/packages/init/src/init.ts b/packages/init/src/init.ts index d362355121a..3ec139b833c 100644 --- a/packages/init/src/init.ts +++ b/packages/init/src/init.ts @@ -48,13 +48,13 @@ Initialize a new Fresh project. This will create all the necessary files for a new project. To generate a project in the './foobar' subdirectory: - ${colors.rgb8("deno run -Ar jsr:@fresh/init ./foobar", 245)} + ${colors.rgb8("deno create @fresh/init ./foobar", 245)} To generate a project in the current directory: - ${colors.rgb8("deno run -Ar jsr:@fresh/init .", 245)} + ${colors.rgb8("deno create @fresh/init .", 245)} ${colors.rgb8("USAGE:", 3)} - ${colors.rgb8("deno run -Ar jsr:@fresh/init [DIRECTORY]", 245)} + ${colors.rgb8("deno create @fresh/init [DIRECTORY]", 245)} ${colors.rgb8("OPTIONS:", 3)} ${colors.rgb8("--force", 2)} Overwrite existing files diff --git a/www/components/homepage/Hero.tsx b/www/components/homepage/Hero.tsx index 73e8999f1f1..b3b760931c7 100644 --- a/www/components/homepage/Hero.tsx +++ b/www/components/homepage/Hero.tsx @@ -14,7 +14,7 @@ export function Hero() { </h2> <div class="mt-12 flex flex-wrap justify-center items-stretch md:justify-start gap-4"> <FancyLink href="/docs/getting-started">Get started</FancyLink> - <CopyArea code={`deno run -Ar jsr:@fresh/init`} /> + <CopyArea code={`deno create @fresh/init`} /> </div> </div> <div class="md:col-span-2 flex justify-center items-end"> From b560aa89b29d48c406e56f306840681d24a95edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= <biwanczuk@gmail.com> Date: Sun, 29 Mar 2026 20:06:59 +0200 Subject: [PATCH 57/59] fmt --- docs/latest/advanced/api-reference.md | 98 +++++------ docs/latest/advanced/app-wrapper.md | 124 +++++++++++++- docs/latest/advanced/opentelemetry.md | 101 ++++++++++- docs/latest/advanced/serialization.md | 35 ++-- docs/latest/advanced/troubleshooting.md | 5 +- docs/latest/concepts/app.md | 7 +- docs/latest/concepts/islands.md | 7 +- docs/latest/concepts/layouts.md | 160 ++++++++++++------ docs/latest/concepts/signals.md | 7 +- docs/latest/concepts/static-files.md | 5 +- docs/latest/deployment/docker.md | 7 +- docs/latest/examples/index.md | 1 - .../index.md} | 0 docs/toc.ts | 18 +- 14 files changed, 406 insertions(+), 169 deletions(-) rename docs/latest/{examples/migration-guide.md => migration-guide/index.md} (100%) diff --git a/docs/latest/advanced/api-reference.md b/docs/latest/advanced/api-reference.md index 9aa76c05efb..ce6a65b96c5 100644 --- a/docs/latest/advanced/api-reference.md +++ b/docs/latest/advanced/api-reference.md @@ -16,46 +16,46 @@ The main entry point for server-side code. import { App, createDefine, HttpError, page, staticFiles } from "fresh"; ``` -| Export | Kind | Description | -| ----------------- | -------- | -------------------------------------------------------------------------------------------------- | -| `App` | Class | The main application class. See [App](/docs/concepts/app). | -| `staticFiles` | Function | Middleware for serving static files. See [Static Files](/docs/concepts/static-files). | -| `createDefine` | Function | Create type-safe `define.*` helpers. See [Define Helpers](/docs/advanced/define). | -| `page` | Function | Return data from a handler to a page component. See [Data Fetching](/docs/concepts/data-fetching). | -| `HttpError` | Class | Throw HTTP errors with status codes. See [Error Handling](/docs/advanced/error-handling). | -| `cors` | Function | CORS middleware. See [cors](/docs/plugins/cors). | -| `csrf` | Function | CSRF protection middleware. See [csrf](/docs/plugins/csrf). | -| `csp` | Function | Content Security Policy middleware. See [csp](/docs/plugins/csp). | -| `trailingSlashes` | Function | Trailing slash enforcement middleware. See [trailingSlashes](/docs/plugins/trailing-slashes). | +| Export | Kind | Description | +| --------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------- | +| [`App`](https://jsr.io/@fresh/core/doc/~/App) | Class | The main application class. See [App](/docs/concepts/app). | +| [`staticFiles`](https://jsr.io/@fresh/core/doc/~/staticFiles) | Function | Middleware for serving static files. See [Static Files](/docs/concepts/static-files). | +| [`createDefine`](https://jsr.io/@fresh/core/doc/~/createDefine) | Function | Create type-safe `define.*` helpers. See [Define Helpers](/docs/advanced/define). | +| [`page`](https://jsr.io/@fresh/core/doc/~/page) | Function | Return data from a handler to a page component. See [Data Fetching](/docs/concepts/data-fetching). | +| [`HttpError`](https://jsr.io/@fresh/core/doc/~/HttpError) | Class | Throw HTTP errors with status codes. See [Error Handling](/docs/advanced/error-handling). | +| [`cors`](https://jsr.io/@fresh/core/doc/~/cors) | Function | CORS middleware. See [cors](/docs/plugins/cors). | +| [`csrf`](https://jsr.io/@fresh/core/doc/~/csrf) | Function | CSRF protection middleware. See [csrf](/docs/plugins/csrf). | +| [`csp`](https://jsr.io/@fresh/core/doc/~/csp) | Function | Content Security Policy middleware. See [csp](/docs/plugins/csp). | +| [`trailingSlashes`](https://jsr.io/@fresh/core/doc/~/trailingSlashes) | Function | Trailing slash enforcement middleware. See [trailingSlashes](/docs/plugins/trailing-slashes). | **Types:** -| Export | Description | -| ------------------------------------- | ------------------------------------------------------------------------------------ | -| `Context` / `FreshContext` | The request context passed to all middlewares and handlers. | -| `PageProps` | Props received by page components (includes `data`, `url`, `params`, `state`, etc.). | -| `Middleware` / `MiddlewareFn` | Middleware function type. | -| `HandlerFn` | Single handler function type. | -| `HandlerByMethod` | Object with per-method handler functions. | -| `RouteHandler` | Union of `HandlerFn` and `HandlerByMethod`. | -| `PageResponse` | Return type of `page()`. | -| `RouteConfig` | Route configuration (`routeOverride`, `skipInheritedLayouts`, etc.). | -| `LayoutConfig` | Layout configuration (`skipInheritedLayouts`, `skipAppWrapper`). | -| `Define` | Type of the object returned by `createDefine()`. | -| `FreshConfig` / `ResolvedFreshConfig` | App configuration types. | -| `ListenOptions` | Options for `app.listen()`. | -| `Island` | Island component type. | -| `Method` | HTTP method union type. | -| `RouteData` | Data type returned by route handlers via `page()`. | -| `Lazy` / `MaybeLazy` | Utility types for lazily-loaded routes and middleware. | -| `CORSOptions` | Options for `cors()`. | -| `CsrfOptions` | Options for `csrf()`. | -| `CSPOptions` | Options for `csp()`. | +| Export | Kind | Description | +| --------------------------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------- | +| [`Context`](https://jsr.io/@fresh/core/doc/~/Context) / [`FreshContext`](https://jsr.io/@fresh/core/doc/~/FreshContext) | Interface | The request context passed to all middlewares and handlers. | +| [`PageProps`](https://jsr.io/@fresh/core/doc/~/PageProps) | Type | Props received by page components (`data`, `url`, `params`, `state`, etc.). | +| [`Middleware`](https://jsr.io/@fresh/core/doc/~/Middleware) / [`MiddlewareFn`](https://jsr.io/@fresh/core/doc/~/MiddlewareFn) | Type | Middleware function type. | +| [`HandlerFn`](https://jsr.io/@fresh/core/doc/~/HandlerFn) | Type | Single handler function type. | +| [`HandlerByMethod`](https://jsr.io/@fresh/core/doc/~/HandlerByMethod) | Type | Object with per-method handler functions. | +| [`RouteHandler`](https://jsr.io/@fresh/core/doc/~/RouteHandler) | Type | Union of `HandlerFn` and `HandlerByMethod`. | +| [`PageResponse`](https://jsr.io/@fresh/core/doc/~/PageResponse) | Type | Return type of `page()`. | +| [`RouteConfig`](https://jsr.io/@fresh/core/doc/~/RouteConfig) | Interface | Route configuration (`routeOverride`, `skipInheritedLayouts`, etc.). | +| [`LayoutConfig`](https://jsr.io/@fresh/core/doc/~/LayoutConfig) | Interface | Layout configuration (`skipInheritedLayouts`, `skipAppWrapper`). | +| [`Define`](https://jsr.io/@fresh/core/doc/~/Define) | Interface | Type of the object returned by `createDefine()`. | +| [`FreshConfig`](https://jsr.io/@fresh/core/doc/~/FreshConfig) / [`ResolvedFreshConfig`](https://jsr.io/@fresh/core/doc/~/ResolvedFreshConfig) | Interface | App configuration types. | +| [`ListenOptions`](https://jsr.io/@fresh/core/doc/~/ListenOptions) | Interface | Options for `app.listen()`. | +| [`Island`](https://jsr.io/@fresh/core/doc/~/Island) | Type | Island component type. | +| [`Method`](https://jsr.io/@fresh/core/doc/~/Method) | Type | HTTP method union type. | +| [`RouteData`](https://jsr.io/@fresh/core/doc/~/RouteData) | Type | Data type returned by route handlers via `page()`. | +| [`Lazy`](https://jsr.io/@fresh/core/doc/~/Lazy) / [`MaybeLazy`](https://jsr.io/@fresh/core/doc/~/MaybeLazy) | Type | Utility types for lazily-loaded routes and middleware. | +| [`CORSOptions`](https://jsr.io/@fresh/core/doc/~/CORSOptions) | Interface | Options for `cors()`. | +| [`CsrfOptions`](https://jsr.io/@fresh/core/doc/~/CsrfOptions) | Interface | Options for `csrf()`. | +| [`CSPOptions`](https://jsr.io/@fresh/core/doc/~/CSPOptions) | Interface | Options for `csp()`. | ## `fresh/runtime` Shared runtime utilities for both server and client code. Safe to import in -islands. +[islands](/docs/concepts/islands). ```ts import { @@ -68,14 +68,14 @@ import { } from "fresh/runtime"; ``` -| Export | Kind | Description | -| ------------- | --------- | ---------------------------------------------------------------------------------------------- | -| `IS_BROWSER` | Constant | `true` in the browser, `false` on the server. Use to guard browser-only code. | -| `asset` | Function | Add cache-busting query params to asset URLs. See [Static Files](/docs/concepts/static-files). | -| `assetSrcSet` | Function | Apply `asset()` to all URLs in a `srcset` string. | -| `Partial` | Component | Mark a region for partial updates. See [Partials](/docs/advanced/partials). | -| `Head` | Component | Add elements to the document `<head>`. See [Modifying head](/docs/advanced/head). | -| `HttpError` | Class | HTTP error class (re-exported from `fresh`). | +| Export | Kind | Description | +| --------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------- | +| [`IS_BROWSER`](https://jsr.io/@fresh/core/doc/runtime/~/IS_BROWSER) | Constant | `true` in the browser, `false` on the server. Use to guard browser-only code. | +| [`asset`](https://jsr.io/@fresh/core/doc/runtime/~/asset) | Function | Add cache-busting query params to asset URLs. See [Static Files](/docs/concepts/static-files). | +| [`assetSrcSet`](https://jsr.io/@fresh/core/doc/runtime/~/assetSrcSet) | Function | Apply `asset()` to all URLs in a `srcset` string. | +| [`Partial`](https://jsr.io/@fresh/core/doc/runtime/~/Partial) | Component | Mark a region for partial updates. See [Partials](/docs/advanced/partials). | +| [`Head`](https://jsr.io/@fresh/core/doc/runtime/~/Head) | Component | Add elements to the document `<head>`. See [<head> element](/docs/advanced/head). | +| [`HttpError`](https://jsr.io/@fresh/core/doc/runtime/~/HttpError) | Class | HTTP error class (re-exported from `fresh`). | ## `fresh/dev` @@ -85,14 +85,14 @@ Development and build tools. Only used in `dev.ts` (legacy) or build scripts. import { Builder } from "fresh/dev"; ``` -| Export | Kind | Description | -| --------- | ----- | ---------------------------------------------------------------------- | -| `Builder` | Class | Pre-Vite build system (legacy). See [Builder](/docs/advanced/builder). | +| Export | Kind | Description | +| --------------------------------------------------------- | ----- | ---------------------------------------------------------------------- | +| [`Builder`](https://jsr.io/@fresh/core/doc/dev/~/Builder) | Class | Pre-Vite build system (legacy). See [Builder](/docs/advanced/builder). | **Types:** -| Export | Description | -| -------------------------------------------------------- | ----------------------------- | -| `BuildOptions` | Options for `new Builder()`. | -| `ResolvedBuildConfig` | Resolved build configuration. | -| `OnTransformArgs` / `OnTransformOptions` / `TransformFn` | Build plugin hook types. | +| Export | Kind | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------------------- | +| [`BuildOptions`](https://jsr.io/@fresh/core/doc/dev/~/BuildOptions) | Interface | Options for `new Builder()`. | +| [`ResolvedBuildConfig`](https://jsr.io/@fresh/core/doc/dev/~/ResolvedBuildConfig) | Interface | Resolved build configuration. | +| [`OnTransformArgs`](https://jsr.io/@fresh/core/doc/dev/~/OnTransformArgs) / [`OnTransformOptions`](https://jsr.io/@fresh/core/doc/dev/~/OnTransformOptions) / [`TransformFn`](https://jsr.io/@fresh/core/doc/dev/~/TransformFn) | Type | Build plugin hook types. | diff --git a/docs/latest/advanced/app-wrapper.md b/docs/latest/advanced/app-wrapper.md index 54baa325b21..752a31c2c4d 100644 --- a/docs/latest/advanced/app-wrapper.md +++ b/docs/latest/advanced/app-wrapper.md @@ -1,12 +1,51 @@ --- description: | - Add a global app wrapper to provide common meta tags or context for application routes. + The app wrapper defines the outermost HTML shell shared by all pages - the <html>, <head>, and <body> tags. --- -The app wrapper component is a Preact component that represents the outer -structure of the HTML document, typically up until the `<body>`-tag. It is only -rendered on the server and never on the client. The passed `Component` value -represents the children of this component. +The app wrapper is the outermost component in Fresh's rendering hierarchy. It +defines the `<html>`, `<head>`, and `<body>` tags that every page shares. It is +only rendered on the server. + +## When to use an app wrapper + +Use an app wrapper when you need to: + +- Set the document language (`<html lang="en">`) +- Include global `<meta>` tags, fonts, or stylesheets +- Add analytics scripts or structured data to every page +- Set a global `<body>` class or data attribute +- Provide a consistent HTML skeleton without repeating it in every layout + +If you're using [file-based routing](/docs/concepts/file-routing), create a +`routes/_app.tsx` file. Otherwise, register it programmatically with +`app.appWrapper()`. + +## Basic example + +```tsx routes/_app.tsx +import { define } from "../utils.ts"; + +export default define.page(({ Component, url }) => { + return ( + <html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>My App + + + + + + + ); +}); +``` + +## Programmatic registration + +When building your app with `new App()` instead of file-based routing: ```tsx main.tsx function AppWrapper({ Component }) { @@ -26,8 +65,75 @@ function AppWrapper({ Component }) { app.appWrapper(AppWrapper); ``` -Every [`ctx.render()`](/docs/concepts/context#render-1) call will include the -app wrapper component by default, unless opted out. +Only one app wrapper is supported per [`App`](/docs/concepts/app) instance. + +## How it fits in the render hierarchy + +When Fresh renders a page, the components nest like this: + +1. **App wrapper** (`_app.tsx`) - outermost, provides ``/``/`` +2. **[Layouts](/docs/concepts/layouts)** (`_layout.tsx`) - shared page chrome + (nav, sidebar, footer) +3. **Page component** - the route itself + +The app wrapper wraps everything. Layouts sit inside it and wrap the page. + +## Accessing request data + +The app wrapper receives the same props as page components - `url`, `state`, +`params`, and more. This is useful for conditional logic: + +```tsx routes/_app.tsx +import { define } from "../utils.ts"; -Note that only one app wrapper component is supported per -[`App`](/docs/concepts/app) instance. +export default define.page(({ Component, url, state }) => { + return ( + + + + + My App + + + + + + + + ); +}); +``` + +## Skipping the app wrapper + +Some routes may need to bypass the app wrapper entirely - for example, API +routes that return JSON, or pages that need a completely different HTML +structure. Use `skipAppWrapper` in the route config: + +```tsx routes/embed.tsx +import { type RouteConfig } from "fresh"; +import { define } from "../utils.ts"; + +export const config: RouteConfig = { + skipAppWrapper: true, +}; + +export default define.page(() => { + return ( + + + Embed + + +
Embeddable widget
+ + + ); +}); +``` + +When using programmatic layouts, pass `skipAppWrapper` as an option: + +```ts main.ts +app.layout("/embed", EmbedLayout, { skipAppWrapper: true }); +``` diff --git a/docs/latest/advanced/opentelemetry.md b/docs/latest/advanced/opentelemetry.md index 01f897515d5..49ef0becde5 100644 --- a/docs/latest/advanced/opentelemetry.md +++ b/docs/latest/advanced/opentelemetry.md @@ -5,7 +5,8 @@ description: | Fresh automatically instruments key operations with [OpenTelemetry](https://opentelemetry.io/) spans, giving you visibility into how -requests flow through your application. +requests flow through your application. No code changes are needed - just +configure an exporter. ## What's instrumented @@ -14,9 +15,14 @@ Fresh creates spans for: - **[Middleware](/docs/concepts/middleware) execution** - each middleware in the chain - **Route handler execution** - handler function calls -- **Rendering** - server-side page rendering -- **Static file serving** - file lookups and responses -- **Lazy route loading** - dynamic imports of route modules +- **Rendering** - server-side page rendering, including async components +- **[Static file](/docs/concepts/static-files) serving** - file lookups, + caching, and responses +- **Lazy route loading** - dynamic imports of route modules on first access + +All spans are created under the `fresh` tracer (named with the current Fresh +version). The root span for each request includes `http.route` with the matched +route pattern (e.g. `GET /blog/:slug`), making it easy to group traces by route. ## Enabling tracing @@ -24,8 +30,8 @@ Fresh uses the `@opentelemetry/api` package (the vendor-neutral API). Spans are created automatically - you just need to provide an OpenTelemetry SDK and exporter to collect them. -If no exporter is configured, the spans are silently discarded (no performance -overhead). +If no exporter is configured, the spans are silently discarded with no +performance overhead. ### With Deno's built-in OpenTelemetry @@ -37,10 +43,87 @@ Enable it with the `OTEL_DENO` environment variable: OTEL_DENO=true deno task start ``` -This exports traces to an OTLP-compatible collector (configure the endpoint with -`OTEL_EXPORTER_OTLP_ENDPOINT`). +This exports traces to an OTLP-compatible collector. Configure the endpoint: + +```sh Terminal +OTEL_DENO=true \ +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \ +deno task start +``` ### With Deno Deploy [Deno Deploy](/docs/deployment/deno-deploy) collects Fresh traces automatically -when using the Fresh preset - no configuration needed. +when using the Fresh preset - no configuration needed. Traces appear in the Deno +Deploy dashboard. + +### With a custom exporter + +You can use any OpenTelemetry-compatible backend (Jaeger, Zipkin, Honeycomb, +Grafana Tempo, Datadog, etc.). Install the SDK and configure an exporter in your +entry point: + +```ts main.ts +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; + +const sdk = new NodeSDK({ + traceExporter: new OTLPTraceExporter({ + url: "https://your-collector.example.com/v1/traces", + }), +}); +sdk.start(); +``` + +## Span attributes + +Fresh sets the following attributes on spans: + +| Attribute | Span type | Description | +| ----------------- | ----------- | -------------------------------------------------------------------------- | +| `http.route` | Root | The matched route pattern (e.g. `/blog/:slug`) | +| `fresh.span_type` | Various | Internal span classification (`render`, etc.) | +| `fresh.cache` | Static file | Cache status (`immutable`, `not_modified`, `no_cache`, `invalid_bust_key`) | +| `fresh.cache_key` | Static file | The cache bust key for the asset | + +Errors in handlers or rendering are recorded on the span with +`span.recordException()` and the span status is set to `ERROR`. + +## Local development + +### Console exporter + +The quickest way to see traces is to print them to the console. Deno (2.7+) +supports this out of the box - no extra dependencies or services needed: + +```sh Terminal +OTEL_DENO=true \ +OTEL_TRACES_EXPORTER=console \ +deno task start +``` + +Each request prints its spans directly to stderr, showing the full breakdown of +middleware, handler, and rendering timings. + +### Jaeger + +For a visual trace explorer, run [Jaeger](https://www.jaegertracing.io/) locally +with Docker: + +```sh Terminal +docker run -d --name jaeger \ + -p 16686:16686 \ + -p 4317:4317 \ + jaegertracing/all-in-one:latest +``` + +Then start your Fresh app pointing at the Jaeger collector: + +```sh Terminal +OTEL_DENO=true \ +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \ +deno task start +``` + +Open `http://localhost:16686` to browse traces. You'll see each request broken +down into its middleware, handler, and rendering spans. diff --git a/docs/latest/advanced/serialization.md b/docs/latest/advanced/serialization.md index def92861093..3cf981c6465 100644 --- a/docs/latest/advanced/serialization.md +++ b/docs/latest/advanced/serialization.md @@ -12,23 +12,24 @@ custom serialization system that supports more types than standard The following types can be passed as island props: -| Type | Notes | -| ------------------------------------ | -------------------------------------------------------------- | -| `string`, `number`, `boolean` | Primitive types | -| `null`, `undefined` | | -| `bigint` | | -| `NaN`, `Infinity`, `-Infinity`, `-0` | Special numeric values | -| `Array` | Including sparse arrays | -| Plain objects | Objects with string keys and serializable values | -| `Date` | | -| `URL` | | -| `RegExp` | Including flags | -| `Set` | Values must be serializable | -| `Map` | Keys and values must be serializable | -| `Uint8Array` | Binary data | -| `Signal` | From `@preact/signals` - see [Signals](/docs/concepts/signals) | -| `Computed Signal` | Read-only signals | -| JSX Elements | Server-rendered JSX passed to islands | +| Type | Notes | +| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | +| `string`, `number`, `boolean` | Primitive types | +| `null`, `undefined` | | +| `bigint` | | +| `NaN`, `Infinity`, `-Infinity`, `-0` | Special numeric values | +| `Array` | Including sparse arrays | +| Plain objects | Objects with string keys and serializable values | +| `Date` | | +| `URL` | | +| `RegExp` | Including flags | +| `Set` | Values must be serializable | +| `Map` | Keys and values must be serializable | +| `Uint8Array` | Binary data | +| `Signal` | From `@preact/signals` - see [Signals](/docs/concepts/signals) | +| `Computed Signal` | Read-only signals | +| `Temporal.*` | `Instant`, `ZonedDateTime`, `PlainDate`, `PlainTime`, `PlainDateTime`, `PlainYearMonth`, `PlainMonthDay`, `Duration` | +| JSX Elements | Server-rendered JSX passed to islands | ## Not serializable diff --git a/docs/latest/advanced/troubleshooting.md b/docs/latest/advanced/troubleshooting.md index 72980c62357..af4753456a2 100644 --- a/docs/latest/advanced/troubleshooting.md +++ b/docs/latest/advanced/troubleshooting.md @@ -12,8 +12,7 @@ The Node compatibility layer in Deno is constantly receiving new improvements and bug fixes with every Deno version. Chances are that the issue you're running into has already been fixed. To rule that out, install the latest Deno version. -```sh -# Install latest Deno version +```sh Terminal deno upgrade ``` @@ -22,7 +21,7 @@ deno upgrade When run for the first time, you might see Deno complaining about missing packages. Install them with: -```shell +```sh Terminal deno install --allow-scripts ``` diff --git a/docs/latest/concepts/app.md b/docs/latest/concepts/app.md index 271c3b9b9f7..51f403143ea 100644 --- a/docs/latest/concepts/app.md +++ b/docs/latest/concepts/app.md @@ -16,10 +16,9 @@ const app = new App() app.listen(); ``` -> [!TIP] -> To use JSX in your `main` file (e.g. with `ctx.render(

Hello

)`), -> rename it to `main.tsx` and set `serverEntry: "main.tsx"` in the `fresh()` -> plugin options in `vite.config.ts`. +> [tip]: To use JSX in your `main` file (e.g. with +> `ctx.render(

Hello

)`), rename it to `main.tsx` and set +> `serverEntry: "main.tsx"` in the `fresh()` plugin options in `vite.config.ts`. ## Configuration diff --git a/docs/latest/concepts/islands.md b/docs/latest/concepts/islands.md index 9f0fa66e43e..b2f88a7efbc 100644 --- a/docs/latest/concepts/islands.md +++ b/docs/latest/concepts/islands.md @@ -52,6 +52,8 @@ types of values: - `RegExp` - `JSX` Elements - Collections `Map` and `Set` +- `Temporal` objects (`Instant`, `ZonedDateTime`, `PlainDate`, `PlainTime`, + `PlainDateTime`, `PlainYearMonth`, `PlainMonthDay`, `Duration`) - Plain objects with string keys and serializable values - Arrays containing serializable values - Preact [Signals](/docs/concepts/signals) (if the inner value is serializable) @@ -196,6 +198,5 @@ export function ShoelaceButton() { } ``` -> [!TIP] -> Return a plain HTML fallback from the server-side branch (`!IS_BROWSER`) so -> the page is usable before JavaScript loads. +> [tip]: Return a plain HTML fallback from the server-side branch +> (`!IS_BROWSER`) so the page is usable before JavaScript loads. diff --git a/docs/latest/concepts/layouts.md b/docs/latest/concepts/layouts.md index bae5f6f6fea..be4c0249c5e 100644 --- a/docs/latest/concepts/layouts.md +++ b/docs/latest/concepts/layouts.md @@ -1,42 +1,66 @@ --- description: | - Add a layout to provide common meta tags, context for application sub routes, and common layout. + Wrap pages in shared UI using _layout.tsx files. Layouts nest automatically, support async data loading, and can be skipped per route. --- This page covers **file-based layouts** using `_layout.tsx` files. If you're defining routes programmatically with `new App()`, see [Layouts (programmatic)](/docs/advanced/layouts) instead. -A layout is defined in a `_layout.tsx` file in any sub directory (at any level) -under the `routes/` folder. It must contain a default export that is a regular -Preact component. Only one such layout is allowed per sub directory. +Layouts let you wrap groups of pages in shared UI - navigation bars, sidebars, +footers, or any common structure. They are defined in `_layout.tsx` files and +nest automatically based on the directory tree. + +## How layouts work + +Place a `_layout.tsx` file in any directory under `routes/`. It wraps every page +in that directory and its subdirectories. You can have one layout per directory. ```txt-files Project structure └── routes -    ├── sub -    │ ├── page.tsx -    │   └── index.tsx - ├── other - │ ├── _layout.tsx # will be applied on top of `routes/_layout.tsx` - │ └── page.tsx - ├── _layout.tsx # will be applied to all routes - └── _app.tsx + ├── _app.tsx # App wrapper (outermost HTML shell) + ├── _layout.tsx # Root layout - wraps all pages + ├── index.tsx + ├── about.tsx + ├── blog + │ ├── _layout.tsx # Blog layout - wraps blog pages + │ ├── index.tsx + │ └── [slug].tsx + └── admin + ├── _layout.tsx # Admin layout - wraps admin pages + └── dashboard.tsx ``` -The component to be wrapped is received via props, in addition to a few other -things. This allows for the introduction of a global container functioning as a -template which can be conditioned based on state and params. Note that any state -set by [middleware](/docs/concepts/middleware) is available via `props.state`. +When a user visits `/blog/my-post`, Fresh renders these components from the +outside in: -```tsx routes/sub/_layout.tsx -import { define } from "../../utils.ts"; +1. `_app.tsx` - the outer ``/``/`` shell +2. `routes/_layout.tsx` - root layout (e.g. site header and footer) +3. `routes/blog/_layout.tsx` - blog layout (e.g. blog sidebar) +4. `routes/blog/[slug].tsx` - the page itself -export default define.layout(({ Component, state }) => { - // do something with state here +## Basic layout + +A layout receives `Component` (the child to wrap) and other props like `state` +and `url`. Any state set by [middleware](/docs/concepts/middleware) is available +via `props.state`. + +```tsx routes/_layout.tsx +import { define } from "../utils.ts"; + +export default define.layout(({ Component, state, url }) => { return (
- + +
+ +
+
© 2026
); }); @@ -44,20 +68,29 @@ export default define.layout(({ Component, state }) => { ## Async layouts -In case you need to fetch data asynchronously before rendering the layout, you -can use an async layout to do so. +Layouts can be async to fetch data before rendering: -```tsx routes/sub/_layout.tsx +```tsx routes/blog/_layout.tsx import { define } from "../../utils.ts"; export default define.layout(async (ctx) => { - // do something with state here - const data = await loadData(); + const categories = await db.categories.list(); return ( -
-

{data.greeting}

- +
+ +
+ +
); }); @@ -65,45 +98,64 @@ export default define.layout(async (ctx) => { ## Opting out of layout inheritance -Sometimes you want to opt out of the layout inheritance mechanism for a -particular route. This can be done via route configuration. Picture a directory -structure like this: - -```txt-files Project structure -└── /routes -    ├── sub -    │ ├── _layout.tsx -    │ ├── special.tsx # should not inherit layouts -    │   └── index.tsx - └── _layout.tsx -``` - -To make `routes/sub/special.tsx` opt out of rendering layouts we can set -`skipInheritedLayouts: true`. +Sometimes a route needs completely different chrome - a login page, a +full-screen dashboard, or a print view. Use `skipInheritedLayouts` in the route +config to skip all layouts inherited from parent directories: -```tsx routes/sub/special.tsx +```tsx routes/login.tsx import { type RouteConfig } from "fresh"; -import { define } from "../../utils.ts"; +import { define } from "../utils.ts"; export const config: RouteConfig = { - skipInheritedLayouts: true, // Skip already inherited layouts + skipInheritedLayouts: true, }; export default define.page(() => { - return

Hello world

; + return ( + + ); }); ``` -You can skip already inherited layouts inside a layout file: +You can also skip inherited layouts from within a layout file itself. This is +useful when a section of your site needs a completely different shell: -```tsx routes/special/_layout.tsx +```tsx routes/admin/_layout.tsx import { type LayoutConfig } from "fresh"; +import { define } from "../../utils.ts"; export const config: LayoutConfig = { - skipInheritedLayouts: true, // Skip already inherited layouts + skipInheritedLayouts: true, }; -export default function MyPage() { - return

Hello world

; -} +export default define.layout(({ Component, state }) => { + return ( +
+ +
+ +
+
+ ); +}); ``` + +## Layout vs app wrapper + +The [app wrapper](/docs/concepts/app) (`_app.tsx`) and layouts serve different +purposes: + +- **App wrapper** - the outermost ``/``/`` structure. There is + only one, and it wraps everything. +- **Layouts** - reusable UI shells that nest based on directory structure. There + can be many, and they sit between the app wrapper and the page component. diff --git a/docs/latest/concepts/signals.md b/docs/latest/concepts/signals.md index 9d55afec180..42a17cea7a4 100644 --- a/docs/latest/concepts/signals.md +++ b/docs/latest/concepts/signals.md @@ -82,10 +82,9 @@ Both sliders share the same signal - moving one updates the other. When the same signal object is passed to multiple islands, Fresh preserves the reference so they stay synchronized. -> [!NOTE] -> Using `useSignal` in a route component (not an island) is intentional here. -> The signal is created during server rendering, serialized into the HTML, and -> reconstructed as a live signal on the client. This is how Fresh shares +> [info]: Using `useSignal` in a route component (not an island) is intentional +> here. The signal is created during server rendering, serialized into the HTML, +> and reconstructed as a live signal on the client. This is how Fresh shares > reactive state between multiple islands on the same page. ## Shared state across islands diff --git a/docs/latest/concepts/static-files.md b/docs/latest/concepts/static-files.md index 45f31a9a440..e47ae154751 100644 --- a/docs/latest/concepts/static-files.md +++ b/docs/latest/concepts/static-files.md @@ -37,9 +37,8 @@ import "./assets/styles.css"; - Files **referenced by URL path** (favicon.ico, fonts, robots.txt, PDFs, etc.): place in `static/` -> [!TIP] -> Always use root-relative URLs (starting with `/`) when referencing static -> files in HTML. For example, use `src="/image/photo.png"` instead of +> [tip]: Always use root-relative URLs (starting with `/`) when referencing +> static files in HTML. For example, use `src="/image/photo.png"` instead of > `src="image/photo.png"`. Relative paths resolve against the browser's current > URL, which breaks when navigating between routes. diff --git a/docs/latest/deployment/docker.md b/docs/latest/deployment/docker.md index 64b5498f88a..d1c6ba75f14 100644 --- a/docs/latest/deployment/docker.md +++ b/docs/latest/deployment/docker.md @@ -34,10 +34,9 @@ EXPOSE 8000 CMD ["deno", "serve", "-A", "_fresh/server.js"] ``` -> [!NOTE] -> The `deno install --allow-scripts` step is required to populate `node_modules` -> and run any post-install scripts needed by npm packages (e.g. Tailwind CSS). -> This must happen before `deno task build`. +> [info]: The `deno install --allow-scripts` step is required to populate +> `node_modules` and run any post-install scripts needed by npm packages (e.g. +> Tailwind CSS). This must happen before `deno task build`. To build your Docker image inside of a Git repository: diff --git a/docs/latest/examples/index.md b/docs/latest/examples/index.md index 8653ca819e8..957bbe200c9 100644 --- a/docs/latest/examples/index.md +++ b/docs/latest/examples/index.md @@ -7,7 +7,6 @@ In this chapter of the Fresh documentation, you can find examples of features that you may like in your Fresh project. - [API Routes](./examples/api-routes) -- [Migration Guide](./examples/migration-guide) - [DaisyUI](./examples/daisyui) - [Rendering Markdown](./examples/markdown) - [Rendering raw HTML](./examples/rendering-raw-html) diff --git a/docs/latest/examples/migration-guide.md b/docs/latest/migration-guide/index.md similarity index 100% rename from docs/latest/examples/migration-guide.md rename to docs/latest/migration-guide/index.md diff --git a/docs/toc.ts b/docs/toc.ts index 433fb9b00b5..953d10c2544 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -34,18 +34,15 @@ const toc: RawTableOfContents = { link: "latest", pages: [ ["architecture", "Architecture", "link:latest"], + ["islands", "Islands", "link:latest"], ["app", "App", "link:latest"], - ["middleware", "Middlewares", "link:latest"], - ["context", "Context", "link:latest"], - ["routing", "Routing", "link:latest"], ["data-fetching", "Data Fetching", "link:latest"], - - ["islands", "Islands", "link:latest"], + ["middleware", "Middlewares", "link:latest"], + ["context", "Context", "link:latest"], ["signals", "Signals", "link:latest"], - ["static-files", "Static files", "link:latest"], ["layouts", "Layouts", "link:latest"], - + ["static-files", "Static files", "link:latest"], ["file-routing", "File routing", "link:latest"], ], }, @@ -61,7 +58,7 @@ const toc: RawTableOfContents = { ["define", "Define Helpers", "link:latest"], ["serialization", "Serialization", "link:latest"], ["environment-variables", "Environment Variables", "link:latest"], - ["head", "Modifying ", "link:latest"], + ["head", " element", "link:latest"], ["vite", "Vite Plugin Options", "link:latest"], ["opentelemetry", "OpenTelemetry", "link:latest"], ["api-reference", "API Reference", "link:latest"], @@ -98,7 +95,6 @@ const toc: RawTableOfContents = { link: "latest", pages: [ ["api-routes", "API Routes", "link:latest"], - ["migration-guide", "Migration Guide", "link:latest"], ["daisyui", "daisyUI", "link:latest"], ["markdown", "Rendering Markdown", "link:latest"], ["rendering-raw-html", "Rendering raw HTML", "link:latest"], @@ -112,6 +108,10 @@ const toc: RawTableOfContents = { ["common-patterns", "Common Patterns", "link:latest"], ], }, + "migration-guide": { + title: "Migration Guide", + link: "latest", + }, contributing: { title: "Contributing", link: "latest", From e136a7597c314696b7ca707df2283bfce0ccbc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 29 Mar 2026 20:11:01 +0200 Subject: [PATCH 58/59] reset CI From d91adac8bbf496d959ed47932e3f475712e7853e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 29 Mar 2026 20:17:28 +0200 Subject: [PATCH 59/59] fix link --- docs/latest/deployment/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/latest/deployment/index.md b/docs/latest/deployment/index.md index 7c8352a8908..f2bd752a2c0 100644 --- a/docs/latest/deployment/index.md +++ b/docs/latest/deployment/index.md @@ -18,7 +18,7 @@ deno task build > [info]: This runs [vite](/docs/advanced/vite) build under the hood. If you're > migrating from Fresh 1.x and still have a `dev.ts` file, see the -> [migration guide](/docs/examples/migration-guide) for updating your tasks. +> [migration guide](/docs/migration-guide) for updating your tasks. Once completed, it will have created a `_fresh` folder in the project directory which contains the optimized assets.