Skip to content

Commit 103427f

Browse files
bartlomiejuclaude
andauthored
docs: comprehensive documentation audit, fixes, and website improvements (#3712)
## Summary Full documentation audit and fixes for Fresh 2.x docs. Addresses every issue identified in the [audit report](https://gist.github.com/bartlomieju/91ddb08c23552f642d2073c0e9a36102). ### Critical fixes - Remove 4 TODO placeholders (app.md, plugins/index.md) - Fix 4 broken code examples (layouts syntax error, routing missing ctx, csrf wrong import, getting-started unused prop) - Fix 3 copy-pasted frontmatter descriptions (context, env-vars, app) - Fix CSRF page describing CORS instead of CSRF ### New pages (8) - **Architecture** — request lifecycle diagram, server-first rendering, islands architecture - **Data Fetching** — handlers, page(), type-safe data passing, async components, PageProps reference - **Signals** — useSignal, computed, props, shared state, serialization - **Serialization** — supported types, what can't be serialized, circular refs, pitfalls - **API Routes** — handler-only routes, method-specific, catch-all, programmatic - **Common Patterns** — protected routes, redirects, cookies, WebSockets, streaming, content negotiation, subdomain routing, proxying, lazy-loading islands - **API Reference** — all exports from fresh, fresh/runtime, fresh/dev - **OpenTelemetry** — what's instrumented, enabling tracing, Deno Deploy ### Expanded pages (6) - **Routing** — matching priority, HTTP methods, HEAD fallback, file-based handlers - **Vite plugin** — HMR, plugin composition, internals, debugging - **Deno Deploy** — build config, env vars, custom domains, troubleshooting - **deno compile** — cross-compilation, limitations - **Error handling** — expanded HttpError with message param, import paths - **Plugins index** — plugin authoring guidance, built-in plugin list ### Structural improvements - Update all 3 index pages (concepts, advanced, examples) with links to every sub-page - Cross-link the two Layouts pages (file-based vs programmatic) - Resolve contradictory build commands (remove legacy dev.ts references) - Add "next steps" to Getting Started - Explain `(_islands)` and `(_components)` directories in file routing - Document `basePath`, `assetSrcSet()`, `IS_BROWSER`, route `css` option - Add PR guidelines to Contributing page - Add Deno version requirements to Introduction ### Docs fixes from issue triage - **Docker deployment** — add missing `deno install --allow-scripts` step, remove stale `deno cache`, fix CMD (#3500) - **Deployment index** — add install step before build (#3500) - **Static files** — add tip about root-relative URLs (#3482), add image optimization section with vite-imagetools, CDN services, best practices (#671) - **main.ts vs main.tsx** — fix examples showing JSX in main.ts, add tip about serverEntry config (#3476) - **Head component** — clarify dedup precedence: last-rendered wins (#2749) - **Islands** — add custom elements / web components section (#2741) - **Sharing state** — rewrite cart example to avoid shared module-level signals (#2437, #1877) - **Common patterns** — add subdomain routing (#603), proxying requests (#361), lazy-loading islands (#565) ### Website fixes - **Canary redirects** — catch-all `/docs/canary/...` → `/docs/...` redirect (#3375) - **Recipe partials** — redirect direct access to recipe routes to homepage (#2559) - **Homepage** — add progressive complexity code examples to Simple section (#2755) ### Image reference fixes - Rename `getting-started-3-cotuntdown.png` → `getting-started-3-countdown.png` - Fix 1.x partials image path ### Grammar/typos - 11 corrections across 12 files ### CI - Add `docs/check_images_test.ts` — verifies all markdown image references resolve to existing files in `www/static/` Closes #3514 Closes #3476 Closes #3591 Closes #2876 Closes #2075 Closes #1612 Closes #1535 Closes #527 Closes #3059 Closes #3695 Closes #3548 Closes #3500 Closes #3482 Closes #3375 Closes #2749 Closes #2741 Closes #2559 Closes #2437 Closes #671 Closes #361 --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 059183b commit 103427f

59 files changed

Lines changed: 2386 additions & 295 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ with workspace members in `packages/*` and `www/`.
1212
- **`packages/plugin-vite/`** (`@fresh/plugin-vite`): Vite integration plugin
1313
with dev server, SSR/client builds, and HMR.
1414
- **`packages/init/`** (`@fresh/init`): Project scaffolding
15-
(`deno run -A jsr:@fresh/init`).
15+
(`deno create @fresh/init`).
1616
- **`packages/update/`** (`@fresh/update`): Automated Fresh 1.x to 2.x migration
1717
tool using ts-morph for AST transforms.
1818
- **`packages/build-id/`** (`@fresh/build-id`): Build/deployment ID generation.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ You can scaffold a new project by running the Fresh init script. To scaffold a
2929
project run the following:
3030

3131
```sh
32-
deno run -Ar jsr:@fresh/init
32+
deno create @fresh/init
3333
```
3434

3535
Then navigate to the newly created project folder:

docs/1.x/concepts/partials.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Let's use a typical documentation page layout as an example. It often features a
7676
main content area and a sidebar of links to switch between pages of the
7777
documentation (marked green here).
7878

79-
![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)
79+
![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)
8080

8181
The code for such a page (excluding styling) might look like this:
8282

docs/check_images_test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Verifies that all image references in documentation markdown files
3+
* point to existing files in www/static/.
4+
*
5+
* Run: deno test -A docs/check_images_test.ts
6+
*/
7+
import { walk } from "jsr:@std/fs@1/walk";
8+
import { join, resolve } from "jsr:@std/path@1";
9+
10+
const ROOT = resolve(import.meta.dirname!);
11+
const PROJECT_ROOT = resolve(ROOT, "..");
12+
const DOCS_DIR = ROOT;
13+
const STATIC_DIR = join(PROJECT_ROOT, "www", "static");
14+
15+
const IMAGE_RE = /!\[.*?\]\(([^)]+)\)/g;
16+
17+
Deno.test("all doc image references point to existing files", async () => {
18+
const errors: string[] = [];
19+
20+
for await (
21+
const entry of walk(DOCS_DIR, {
22+
exts: [".md", ".mdx"],
23+
includeDirs: false,
24+
})
25+
) {
26+
const content = await Deno.readTextFile(entry.path);
27+
const relativePath = entry.path.slice(PROJECT_ROOT.length);
28+
29+
for (const match of content.matchAll(IMAGE_RE)) {
30+
const imgPath = match[1];
31+
32+
// Skip external URLs
33+
if (imgPath.startsWith("http://") || imgPath.startsWith("https://")) {
34+
continue;
35+
}
36+
37+
// Doc image paths like "/docs/foo.png" map to "www/static/docs/foo.png"
38+
const fsPath = join(STATIC_DIR, imgPath);
39+
40+
try {
41+
await Deno.stat(fsPath);
42+
} catch {
43+
errors.push(`${relativePath}: image not found: ${imgPath}`);
44+
}
45+
}
46+
}
47+
48+
if (errors.length > 0) {
49+
throw new Error(
50+
`Found ${errors.length} broken image reference(s):\n\n${
51+
errors.join("\n")
52+
}`,
53+
);
54+
}
55+
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
description: |
3+
Quick reference for all public exports from Fresh's entry points: fresh, fresh/runtime, and fresh/dev.
4+
---
5+
6+
This page lists all public exports from Fresh's entry points.
7+
8+
> [info]: You can also explore Fresh's full API documentation on JSR:
9+
> [`@fresh/core`](https://jsr.io/@fresh/core/doc)
10+
11+
## `fresh`
12+
13+
The main entry point for server-side code.
14+
15+
```ts
16+
import { App, createDefine, HttpError, page, staticFiles } from "fresh";
17+
```
18+
19+
| Export | Kind | Description |
20+
| --------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------- |
21+
| [`App`](https://jsr.io/@fresh/core/doc/~/App) | Class | The main application class. See [App](/docs/concepts/app). |
22+
| [`staticFiles`](https://jsr.io/@fresh/core/doc/~/staticFiles) | Function | Middleware for serving static files. See [Static Files](/docs/concepts/static-files). |
23+
| [`createDefine`](https://jsr.io/@fresh/core/doc/~/createDefine) | Function | Create type-safe `define.*` helpers. See [Define Helpers](/docs/advanced/define). |
24+
| [`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). |
25+
| [`HttpError`](https://jsr.io/@fresh/core/doc/~/HttpError) | Class | Throw HTTP errors with status codes. See [Error Handling](/docs/advanced/error-handling). |
26+
| [`cors`](https://jsr.io/@fresh/core/doc/~/cors) | Function | CORS middleware. See [cors](/docs/plugins/cors). |
27+
| [`csrf`](https://jsr.io/@fresh/core/doc/~/csrf) | Function | CSRF protection middleware. See [csrf](/docs/plugins/csrf). |
28+
| [`csp`](https://jsr.io/@fresh/core/doc/~/csp) | Function | Content Security Policy middleware. See [csp](/docs/plugins/csp). |
29+
| [`trailingSlashes`](https://jsr.io/@fresh/core/doc/~/trailingSlashes) | Function | Trailing slash enforcement middleware. See [trailingSlashes](/docs/plugins/trailing-slashes). |
30+
31+
**Types:**
32+
33+
| Export | Kind | Description |
34+
| --------------------------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------- |
35+
| [`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. |
36+
| [`PageProps`](https://jsr.io/@fresh/core/doc/~/PageProps) | Type | Props received by page components (`data`, `url`, `params`, `state`, etc.). |
37+
| [`Middleware`](https://jsr.io/@fresh/core/doc/~/Middleware) / [`MiddlewareFn`](https://jsr.io/@fresh/core/doc/~/MiddlewareFn) | Type | Middleware function type. |
38+
| [`HandlerFn`](https://jsr.io/@fresh/core/doc/~/HandlerFn) | Type | Single handler function type. |
39+
| [`HandlerByMethod`](https://jsr.io/@fresh/core/doc/~/HandlerByMethod) | Type | Object with per-method handler functions. |
40+
| [`RouteHandler`](https://jsr.io/@fresh/core/doc/~/RouteHandler) | Type | Union of `HandlerFn` and `HandlerByMethod`. |
41+
| [`PageResponse`](https://jsr.io/@fresh/core/doc/~/PageResponse) | Type | Return type of `page()`. |
42+
| [`RouteConfig`](https://jsr.io/@fresh/core/doc/~/RouteConfig) | Interface | Route configuration (`routeOverride`, `skipInheritedLayouts`, etc.). |
43+
| [`LayoutConfig`](https://jsr.io/@fresh/core/doc/~/LayoutConfig) | Interface | Layout configuration (`skipInheritedLayouts`, `skipAppWrapper`). |
44+
| [`Define`](https://jsr.io/@fresh/core/doc/~/Define) | Interface | Type of the object returned by `createDefine()`. |
45+
| [`FreshConfig`](https://jsr.io/@fresh/core/doc/~/FreshConfig) / [`ResolvedFreshConfig`](https://jsr.io/@fresh/core/doc/~/ResolvedFreshConfig) | Interface | App configuration types. |
46+
| [`ListenOptions`](https://jsr.io/@fresh/core/doc/~/ListenOptions) | Interface | Options for `app.listen()`. |
47+
| [`Island`](https://jsr.io/@fresh/core/doc/~/Island) | Type | Island component type. |
48+
| [`Method`](https://jsr.io/@fresh/core/doc/~/Method) | Type | HTTP method union type. |
49+
| [`RouteData`](https://jsr.io/@fresh/core/doc/~/RouteData) | Type | Data type returned by route handlers via `page()`. |
50+
| [`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. |
51+
| [`CORSOptions`](https://jsr.io/@fresh/core/doc/~/CORSOptions) | Interface | Options for `cors()`. |
52+
| [`CsrfOptions`](https://jsr.io/@fresh/core/doc/~/CsrfOptions) | Interface | Options for `csrf()`. |
53+
| [`CSPOptions`](https://jsr.io/@fresh/core/doc/~/CSPOptions) | Interface | Options for `csp()`. |
54+
55+
## `fresh/runtime`
56+
57+
Shared runtime utilities for both server and client code. Safe to import in
58+
[islands](/docs/concepts/islands).
59+
60+
```ts
61+
import {
62+
asset,
63+
assetSrcSet,
64+
Head,
65+
HttpError,
66+
IS_BROWSER,
67+
Partial,
68+
} from "fresh/runtime";
69+
```
70+
71+
| Export | Kind | Description |
72+
| --------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------- |
73+
| [`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. |
74+
| [`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). |
75+
| [`assetSrcSet`](https://jsr.io/@fresh/core/doc/runtime/~/assetSrcSet) | Function | Apply `asset()` to all URLs in a `srcset` string. |
76+
| [`Partial`](https://jsr.io/@fresh/core/doc/runtime/~/Partial) | Component | Mark a region for partial updates. See [Partials](/docs/advanced/partials). |
77+
| [`Head`](https://jsr.io/@fresh/core/doc/runtime/~/Head) | Component | Add elements to the document `<head>`. See [<head> element](/docs/advanced/head). |
78+
| [`HttpError`](https://jsr.io/@fresh/core/doc/runtime/~/HttpError) | Class | HTTP error class (re-exported from `fresh`). |
79+
80+
## `fresh/dev`
81+
82+
Development and build tools. Only used in `dev.ts` (legacy) or build scripts.
83+
84+
```ts
85+
import { Builder } from "fresh/dev";
86+
```
87+
88+
| Export | Kind | Description |
89+
| --------------------------------------------------------- | ----- | ---------------------------------------------------------------------- |
90+
| [`Builder`](https://jsr.io/@fresh/core/doc/dev/~/Builder) | Class | Pre-Vite build system (legacy). See [Builder](/docs/advanced/builder). |
91+
92+
**Types:**
93+
94+
| Export | Kind | Description |
95+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------------------- |
96+
| [`BuildOptions`](https://jsr.io/@fresh/core/doc/dev/~/BuildOptions) | Interface | Options for `new Builder()`. |
97+
| [`ResolvedBuildConfig`](https://jsr.io/@fresh/core/doc/dev/~/ResolvedBuildConfig) | Interface | Resolved build configuration. |
98+
| [`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. |

docs/latest/advanced/app-wrapper.md

Lines changed: 115 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,51 @@
11
---
22
description: |
3-
Add a global app wrapper to provide common meta tags or context for application routes.
3+
The app wrapper defines the outermost HTML shell shared by all pages - the <html>, <head>, and <body> tags.
44
---
55

6-
The app wrapper component is a Preact component that represents the outer
7-
structure of the HTML document, typically up until the `<body>`-tag. It is only
8-
rendered on the server and never on the client. The passed `Component` value
9-
represents the children of this component.
6+
The app wrapper is the outermost component in Fresh's rendering hierarchy. It
7+
defines the `<html>`, `<head>`, and `<body>` tags that every page shares. It is
8+
only rendered on the server.
9+
10+
## When to use an app wrapper
11+
12+
Use an app wrapper when you need to:
13+
14+
- Set the document language (`<html lang="en">`)
15+
- Include global `<meta>` tags, fonts, or stylesheets
16+
- Add analytics scripts or structured data to every page
17+
- Set a global `<body>` class or data attribute
18+
- Provide a consistent HTML skeleton without repeating it in every layout
19+
20+
If you're using [file-based routing](/docs/concepts/file-routing), create a
21+
`routes/_app.tsx` file. Otherwise, register it programmatically with
22+
`app.appWrapper()`.
23+
24+
## Basic example
25+
26+
```tsx routes/_app.tsx
27+
import { define } from "../utils.ts";
28+
29+
export default define.page(({ Component, url }) => {
30+
return (
31+
<html lang="en">
32+
<head>
33+
<meta charset="utf-8" />
34+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
35+
<title>My App</title>
36+
<link rel="stylesheet" href="/styles.css" />
37+
</head>
38+
<body>
39+
<Component />
40+
</body>
41+
</html>
42+
);
43+
});
44+
```
45+
46+
## Programmatic registration
47+
48+
When building your app with `new App()` instead of file-based routing:
1049

1150
```tsx main.tsx
1251
function AppWrapper({ Component }) {
@@ -26,8 +65,75 @@ function AppWrapper({ Component }) {
2665
app.appWrapper(AppWrapper);
2766
```
2867

29-
Every [`ctx.render()`](/docs/concepts/context#render-1) call will include the
30-
app wrapper component by default, unless opted out.
68+
Only one app wrapper is supported per [`App`](/docs/concepts/app) instance.
69+
70+
## How it fits in the render hierarchy
71+
72+
When Fresh renders a page, the components nest like this:
73+
74+
1. **App wrapper** (`_app.tsx`) - outermost, provides `<html>`/`<head>`/`<body>`
75+
2. **[Layouts](/docs/concepts/layouts)** (`_layout.tsx`) - shared page chrome
76+
(nav, sidebar, footer)
77+
3. **Page component** - the route itself
78+
79+
The app wrapper wraps everything. Layouts sit inside it and wrap the page.
80+
81+
## Accessing request data
82+
83+
The app wrapper receives the same props as page components - `url`, `state`,
84+
`params`, and more. This is useful for conditional logic:
85+
86+
```tsx routes/_app.tsx
87+
import { define } from "../utils.ts";
3188

32-
Note that only one app wrapper component is supported per
33-
[`App`](/docs/concepts/app) instance.
89+
export default define.page(({ Component, url, state }) => {
90+
return (
91+
<html lang="en" data-theme={state.theme ?? "light"}>
92+
<head>
93+
<meta charset="utf-8" />
94+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
95+
<title>My App</title>
96+
<meta property="og:url" content={url.href} />
97+
<link rel="canonical" href={url.href} />
98+
</head>
99+
<body>
100+
<Component />
101+
</body>
102+
</html>
103+
);
104+
});
105+
```
106+
107+
## Skipping the app wrapper
108+
109+
Some routes may need to bypass the app wrapper entirely - for example, API
110+
routes that return JSON, or pages that need a completely different HTML
111+
structure. Use `skipAppWrapper` in the route config:
112+
113+
```tsx routes/embed.tsx
114+
import { type RouteConfig } from "fresh";
115+
import { define } from "../utils.ts";
116+
117+
export const config: RouteConfig = {
118+
skipAppWrapper: true,
119+
};
120+
121+
export default define.page(() => {
122+
return (
123+
<html>
124+
<head>
125+
<title>Embed</title>
126+
</head>
127+
<body>
128+
<div id="widget">Embeddable widget</div>
129+
</body>
130+
</html>
131+
);
132+
});
133+
```
134+
135+
When using programmatic layouts, pass `skipAppWrapper` as an option:
136+
137+
```ts main.ts
138+
app.layout("/embed", EmbedLayout, { skipAppWrapper: true });
139+
```

docs/latest/advanced/builder.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ For more information on how to use tailwindcss, check out
171171
You can customize the tailwind plugin via the following options:
172172

173173
```ts dev.ts
174-
tailwind(builder, app, {
174+
tailwind(builder, {
175175
// Exclude certain files from processing
176176
exclude: ["/admin/**", "*.temp.css"],
177177
// Force optimization (defaults to production mode)
@@ -183,7 +183,7 @@ tailwind(builder, app, {
183183

184184
### Tailwindcss v3
185185

186-
If can't update to the current version of tailwindcss we have a dedicated
186+
If you can't update to the current version of tailwindcss we have a dedicated
187187
`@fresh/plugin-tailwindcss-v3` plugin that uses tailwindcss v3. That way you can
188188
decided on your own when it's best to update to v4.
189189

docs/latest/advanced/define.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
---
22
description: |
3-
Define helpers are a less TypeScripty way to declare middlewares, routes and layouts
3+
Define helpers are a less TypeScripty way to declare [middlewares](/docs/concepts/middleware), routes and [layouts](/docs/concepts/layouts)
44
---
55

66
Define helpers can be used to shorten the amount of types you have to type
77
yourself in code. They are entirely optional as some developers prefer the
8-
explicitness of types, other's like the convenience of `define.*` helpers.
8+
explicitness of types, others like the convenience of `define.*` helpers.
99

1010
Without define helpers:
1111

@@ -72,7 +72,7 @@ export default define.page<typeof handler>((props) => {
7272
});
7373
```
7474

75-
There is also a `define.layout()` helper for layouts:
75+
There is also a `define.layout()` helper for [layouts](/docs/concepts/layouts):
7676

7777
```tsx
7878
export default define.layout((props) => {

0 commit comments

Comments
 (0)