@pyreon/create-zero 0.4.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-zero.js +1 -1
- package/lib/index.js +6 -6
- package/lib/index.js.map +1 -1
- package/package.json +5 -4
- package/templates/default/CLAUDE.md +3 -1
- package/templates/default/env.d.ts +6 -6
- package/templates/default/index.html +1 -1
- package/templates/default/package.json +5 -5
- package/templates/default/src/entry-client.ts +3 -3
- package/templates/default/src/entry-server.ts +9 -13
- package/templates/default/src/features/posts.ts +6 -6
- package/templates/default/src/routes/(admin)/dashboard.tsx +29 -34
- package/templates/default/src/routes/_error.tsx +5 -10
- package/templates/default/src/routes/_layout.tsx +10 -19
- package/templates/default/src/routes/about.tsx +25 -44
- package/templates/default/src/routes/api/health.ts +1 -1
- package/templates/default/src/routes/api/posts.ts +5 -5
- package/templates/default/src/routes/counter.tsx +20 -29
- package/templates/default/src/routes/index.tsx +20 -22
- package/templates/default/src/routes/posts/[id].tsx +34 -34
- package/templates/default/src/routes/posts/index.tsx +29 -46
- package/templates/default/src/routes/posts/new.tsx +12 -16
- package/templates/default/src/stores/app.ts +2 -2
- package/templates/default/vite.config.ts +10 -10
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { useHead } from
|
|
2
|
-
import { Link } from
|
|
1
|
+
import { useHead } from "@pyreon/head"
|
|
2
|
+
import { Link } from "@pyreon/zero/link"
|
|
3
3
|
|
|
4
4
|
export const meta = {
|
|
5
|
-
title:
|
|
6
|
-
description:
|
|
5
|
+
title: "About — Pyreon Zero",
|
|
6
|
+
description: "Learn about the Pyreon Zero meta-framework.",
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export default function About() {
|
|
10
10
|
useHead({
|
|
11
11
|
title: meta.title,
|
|
12
|
-
meta: [{ name:
|
|
12
|
+
meta: [{ name: "description", content: meta.description }],
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
return (
|
|
@@ -17,8 +17,8 @@ export default function About() {
|
|
|
17
17
|
<div class="page-header">
|
|
18
18
|
<h1>About Zero</h1>
|
|
19
19
|
<p>
|
|
20
|
-
A signal-based meta-framework built on Vite — designed for developers
|
|
21
|
-
|
|
20
|
+
A signal-based meta-framework built on Vite — designed for developers who care about
|
|
21
|
+
performance and simplicity.
|
|
22
22
|
</p>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
@@ -43,20 +43,18 @@ export default function About() {
|
|
|
43
43
|
</h2>
|
|
44
44
|
<div style="color: var(--c-text-secondary); line-height: 1.8; display: flex; flex-direction: column; gap: var(--space-lg);">
|
|
45
45
|
<p>
|
|
46
|
-
Most frameworks make you choose between developer experience and
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
no reconciliation, no wasted work.
|
|
46
|
+
Most frameworks make you choose between developer experience and runtime performance.
|
|
47
|
+
Zero doesn't. Pyreon's signal-based reactivity means your components compile to surgical
|
|
48
|
+
DOM updates — no diffing, no reconciliation, no wasted work.
|
|
50
49
|
</p>
|
|
51
50
|
<p>
|
|
52
|
-
On top of that, Zero gives you file-based routing, server-side
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
and SEO utilities — all built in, all zero-config.
|
|
51
|
+
On top of that, Zero gives you file-based routing, server-side rendering, static
|
|
52
|
+
generation, incremental regeneration, font optimization, image optimization, smart
|
|
53
|
+
caching, link prefetching, and SEO utilities — all built in, all zero-config.
|
|
56
54
|
</p>
|
|
57
55
|
<p>
|
|
58
|
-
Deploy to Node, Bun, Vercel, Cloudflare, Netlify, or export as a
|
|
59
|
-
|
|
56
|
+
Deploy to Node, Bun, Vercel, Cloudflare, Netlify, or export as a static site. One
|
|
57
|
+
codebase, any target.
|
|
60
58
|
</p>
|
|
61
59
|
</div>
|
|
62
60
|
</section>
|
|
@@ -67,30 +65,15 @@ export default function About() {
|
|
|
67
65
|
</h2>
|
|
68
66
|
<div style="display: grid; gap: var(--space-sm);">
|
|
69
67
|
{[
|
|
70
|
-
[
|
|
71
|
-
[
|
|
72
|
-
[
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
],
|
|
76
|
-
[
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
],
|
|
80
|
-
[
|
|
81
|
-
'<Image>',
|
|
82
|
-
'Lazy loading, responsive srcset, blur-up placeholders',
|
|
83
|
-
],
|
|
84
|
-
['<Link>', 'Prefetch on hover or viewport entry for instant nav'],
|
|
85
|
-
[
|
|
86
|
-
'Font Plugin',
|
|
87
|
-
'Google Fonts optimization, preconnect, font-display:swap',
|
|
88
|
-
],
|
|
89
|
-
[
|
|
90
|
-
'Cache MW',
|
|
91
|
-
'Immutable hashed assets, stale-while-revalidate pages',
|
|
92
|
-
],
|
|
93
|
-
['SEO Tools', 'Sitemap, robots.txt, JSON-LD structured data'],
|
|
68
|
+
["Pyreon", "Signal-based UI framework with JSX"],
|
|
69
|
+
["Vite", "Lightning-fast dev server and optimized builds"],
|
|
70
|
+
["File Router", "Drop a file, get a route — layouts, guards, loaders"],
|
|
71
|
+
["SSR / SSG / ISR", "Every rendering strategy, per-route overrides"],
|
|
72
|
+
["<Image>", "Lazy loading, responsive srcset, blur-up placeholders"],
|
|
73
|
+
["<Link>", "Prefetch on hover or viewport entry for instant nav"],
|
|
74
|
+
["Font Plugin", "Google Fonts optimization, preconnect, font-display:swap"],
|
|
75
|
+
["Cache MW", "Immutable hashed assets, stale-while-revalidate pages"],
|
|
76
|
+
["SEO Tools", "Sitemap, robots.txt, JSON-LD structured data"],
|
|
94
77
|
].map(([name, desc]) => (
|
|
95
78
|
<div
|
|
96
79
|
class="card"
|
|
@@ -99,9 +82,7 @@ export default function About() {
|
|
|
99
82
|
<code style="min-width: 130px; font-weight: 600; color: var(--c-accent);">
|
|
100
83
|
{name}
|
|
101
84
|
</code>
|
|
102
|
-
<span style="color: var(--c-text-secondary); font-size: 0.9rem;">
|
|
103
|
-
{desc}
|
|
104
|
-
</span>
|
|
85
|
+
<span style="color: var(--c-text-secondary); font-size: 0.9rem;">{desc}</span>
|
|
105
86
|
</div>
|
|
106
87
|
))}
|
|
107
88
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApiContext } from
|
|
1
|
+
import type { ApiContext } from "@pyreon/zero"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* API route example — /api/posts
|
|
@@ -10,9 +10,9 @@ import type { ApiContext } from '@pyreon/zero'
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const POSTS = [
|
|
13
|
-
{ id: 1, title:
|
|
14
|
-
{ id: 2, title:
|
|
15
|
-
{ id: 3, title:
|
|
13
|
+
{ id: 1, title: "Getting Started with Pyreon Zero", published: true },
|
|
14
|
+
{ id: 2, title: "Understanding Signals", published: true },
|
|
15
|
+
{ id: 3, title: "Server-Side Rendering Made Simple", published: false },
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
export function GET(_ctx: ApiContext) {
|
|
@@ -26,7 +26,7 @@ export async function POST(ctx: ApiContext) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
if (!body.title) {
|
|
29
|
-
return Response.json({ error:
|
|
29
|
+
return Response.json({ error: "Title is required" }, { status: 400 })
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const post = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useHead } from
|
|
2
|
-
import { computed, signal } from
|
|
1
|
+
import { useHead } from "@pyreon/head"
|
|
2
|
+
import { computed, signal } from "@pyreon/reactivity"
|
|
3
3
|
|
|
4
4
|
export const meta = {
|
|
5
|
-
title:
|
|
5
|
+
title: "Counter — Pyreon Zero",
|
|
6
6
|
description: "See Pyreon's signal-based reactivity in action.",
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -19,33 +19,29 @@ export default function Counter() {
|
|
|
19
19
|
<span class="badge">Interactive Demo</span>
|
|
20
20
|
<h1 style="margin-top: var(--space-md);">Signal Reactivity</h1>
|
|
21
21
|
<p>
|
|
22
|
-
Fine-grained reactivity with zero virtual DOM. Only the exact text
|
|
23
|
-
|
|
22
|
+
Fine-grained reactivity with zero virtual DOM. Only the exact text nodes that display
|
|
23
|
+
these values are updated — nothing else re-renders.
|
|
24
24
|
</p>
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
27
|
<div class="counter-demo">
|
|
28
|
-
<div class="counter-display">{count}</div>
|
|
28
|
+
<div class="counter-display">{() => count()}</div>
|
|
29
29
|
|
|
30
30
|
<div class="counter-controls">
|
|
31
31
|
<button
|
|
32
32
|
type="button"
|
|
33
33
|
class="btn btn-secondary"
|
|
34
|
-
|
|
34
|
+
onClick={() => count.update((n) => n - 1)}
|
|
35
35
|
>
|
|
36
36
|
-
|
|
37
37
|
</button>
|
|
38
|
-
<button
|
|
39
|
-
type="button"
|
|
40
|
-
class="btn btn-primary"
|
|
41
|
-
onclick={() => count.set(0)}
|
|
42
|
-
>
|
|
38
|
+
<button type="button" class="btn btn-primary" onClick={() => count.set(0)}>
|
|
43
39
|
Reset
|
|
44
40
|
</button>
|
|
45
41
|
<button
|
|
46
42
|
type="button"
|
|
47
43
|
class="btn btn-secondary"
|
|
48
|
-
|
|
44
|
+
onClick={() => count.update((n) => n + 1)}
|
|
49
45
|
>
|
|
50
46
|
+
|
|
51
47
|
</button>
|
|
@@ -53,38 +49,33 @@ export default function Counter() {
|
|
|
53
49
|
|
|
54
50
|
<div class="counter-meta">
|
|
55
51
|
<div>
|
|
56
|
-
count() → <strong>{count}</strong>
|
|
52
|
+
count() → <strong>{() => count()}</strong>
|
|
57
53
|
</div>
|
|
58
54
|
<div>
|
|
59
|
-
doubled() → <strong>{doubled}</strong>
|
|
55
|
+
doubled() → <strong>{() => doubled()}</strong>
|
|
60
56
|
</div>
|
|
61
57
|
<div>
|
|
62
|
-
isEven() → <strong>{() => (isEven() ?
|
|
58
|
+
isEven() → <strong>{() => (isEven() ? "true" : "false")}</strong>
|
|
63
59
|
</div>
|
|
64
60
|
</div>
|
|
65
61
|
</div>
|
|
66
62
|
|
|
67
|
-
<div
|
|
68
|
-
class="code-block"
|
|
69
|
-
style="max-width: 520px; margin: var(--space-2xl) auto 0;"
|
|
70
|
-
>
|
|
63
|
+
<div class="code-block" style="max-width: 520px; margin: var(--space-2xl) auto 0;">
|
|
71
64
|
<div class="code-block-header">
|
|
72
65
|
<span>counter.tsx</span>
|
|
73
66
|
</div>
|
|
74
67
|
<pre>
|
|
75
68
|
<code>
|
|
76
|
-
<span class="kw">import</span> {
|
|
77
|
-
<span class="kw">from</span>{' '}
|
|
69
|
+
<span class="kw">import</span> {"{"} signal, computed {"}"} <span class="kw">from</span>{" "}
|
|
78
70
|
<span class="str">"@pyreon/reactivity"</span>
|
|
79
|
-
<span class="kw">const</span> <span class="fn">count</span> ={
|
|
80
|
-
<span class="fn">signal</span>(<span class="str">0</span>)
|
|
81
|
-
<span class="
|
|
82
|
-
<span class="fn">computed</span>(() =>{' '}
|
|
71
|
+
<span class="kw">const</span> <span class="fn">count</span> ={" "}
|
|
72
|
+
<span class="fn">signal</span>(<span class="str">0</span>)<span class="kw">const</span>{" "}
|
|
73
|
+
<span class="fn">doubled</span> = <span class="fn">computed</span>(() =>{" "}
|
|
83
74
|
<span class="fn">count</span>() * <span class="str">2</span>)
|
|
84
|
-
<span class="cm">{
|
|
85
|
-
<span class="cm">{
|
|
75
|
+
<span class="cm">{"// Just reference the signal in JSX —"}</span>
|
|
76
|
+
<span class="cm">{"// only this text node updates"}</span>
|
|
86
77
|
<span class="tag"><span></span>
|
|
87
|
-
{
|
|
78
|
+
{"{"}count{"}"}
|
|
88
79
|
<span class="tag"></span></span>
|
|
89
80
|
</code>
|
|
90
81
|
</pre>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { useHead } from
|
|
2
|
-
import { Link } from
|
|
1
|
+
import { useHead } from "@pyreon/head"
|
|
2
|
+
import { Link } from "@pyreon/zero/link"
|
|
3
3
|
|
|
4
4
|
export const meta = {
|
|
5
|
-
title:
|
|
6
|
-
description:
|
|
5
|
+
title: "Pyreon Zero",
|
|
6
|
+
description: "The signal-based meta-framework. Build fast, stay fast.",
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export default function Home() {
|
|
10
10
|
useHead({
|
|
11
|
-
title:
|
|
12
|
-
meta: [{ name:
|
|
11
|
+
title: "Pyreon Zero — The Signal-Based Meta-Framework",
|
|
12
|
+
meta: [{ name: "description", content: meta.description }],
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
return (
|
|
@@ -22,9 +22,8 @@ export default function Home() {
|
|
|
22
22
|
<span class="gradient">Stay fast.</span>
|
|
23
23
|
</h1>
|
|
24
24
|
<p>
|
|
25
|
-
Pyreon Zero is a signal-based full-stack framework powered by Vite.
|
|
26
|
-
|
|
27
|
-
don't.
|
|
25
|
+
Pyreon Zero is a signal-based full-stack framework powered by Vite. File-based routing,
|
|
26
|
+
SSR, SSG, ISR — everything you need, nothing you don't.
|
|
28
27
|
</p>
|
|
29
28
|
<div class="hero-actions">
|
|
30
29
|
<Link href="/counter" class="btn btn-primary">
|
|
@@ -75,8 +74,8 @@ export default function Home() {
|
|
|
75
74
|
</div>
|
|
76
75
|
<h3>Signal-Based Reactivity</h3>
|
|
77
76
|
<p>
|
|
78
|
-
Fine-grained reactivity with zero virtual DOM overhead. Only the
|
|
79
|
-
|
|
77
|
+
Fine-grained reactivity with zero virtual DOM overhead. Only the exact DOM nodes that
|
|
78
|
+
need updating are touched.
|
|
80
79
|
</p>
|
|
81
80
|
</div>
|
|
82
81
|
|
|
@@ -98,8 +97,8 @@ export default function Home() {
|
|
|
98
97
|
</div>
|
|
99
98
|
<h3>File-Based Routing</h3>
|
|
100
99
|
<p>
|
|
101
|
-
Drop a file in <code>src/routes/</code> and it's a route. Layouts,
|
|
102
|
-
|
|
100
|
+
Drop a file in <code>src/routes/</code> and it's a route. Layouts, dynamic params,
|
|
101
|
+
catch-alls, and route groups built in.
|
|
103
102
|
</p>
|
|
104
103
|
</div>
|
|
105
104
|
|
|
@@ -123,8 +122,8 @@ export default function Home() {
|
|
|
123
122
|
</div>
|
|
124
123
|
<h3>SSR / SSG / ISR / SPA</h3>
|
|
125
124
|
<p>
|
|
126
|
-
Every rendering strategy out of the box. Per-route overrides let you
|
|
127
|
-
|
|
125
|
+
Every rendering strategy out of the box. Per-route overrides let you mix SSR pages with
|
|
126
|
+
static marketing pages.
|
|
128
127
|
</p>
|
|
129
128
|
</div>
|
|
130
129
|
|
|
@@ -146,9 +145,8 @@ export default function Home() {
|
|
|
146
145
|
</div>
|
|
147
146
|
<h3>Font & Image Optimization</h3>
|
|
148
147
|
<p>
|
|
149
|
-
Automatic Google Fonts inlining, font-display swap, and an{
|
|
150
|
-
|
|
151
|
-
placeholders.
|
|
148
|
+
Automatic Google Fonts inlining, font-display swap, and an <code>{"<Image>"}</code>{" "}
|
|
149
|
+
component with lazy loading and blur-up placeholders.
|
|
152
150
|
</p>
|
|
153
151
|
</div>
|
|
154
152
|
|
|
@@ -170,8 +168,8 @@ export default function Home() {
|
|
|
170
168
|
</div>
|
|
171
169
|
<h3>Smart Caching & Security</h3>
|
|
172
170
|
<p>
|
|
173
|
-
Built-in middleware for immutable asset caching,
|
|
174
|
-
|
|
171
|
+
Built-in middleware for immutable asset caching, stale-while-revalidate, security
|
|
172
|
+
headers, and compression hints.
|
|
175
173
|
</p>
|
|
176
174
|
</div>
|
|
177
175
|
|
|
@@ -195,8 +193,8 @@ export default function Home() {
|
|
|
195
193
|
</div>
|
|
196
194
|
<h3>Deploy Anywhere</h3>
|
|
197
195
|
<p>
|
|
198
|
-
Adapters for Node, Bun, Vercel, Cloudflare, and Netlify. Or export
|
|
199
|
-
|
|
196
|
+
Adapters for Node, Bun, Vercel, Cloudflare, and Netlify. Or export as a fully static
|
|
197
|
+
site.
|
|
200
198
|
</p>
|
|
201
199
|
</div>
|
|
202
200
|
</section>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useHead } from
|
|
2
|
-
import { useLoaderData } from
|
|
3
|
-
import type { LoaderContext } from
|
|
4
|
-
import { Link } from
|
|
1
|
+
import { useHead } from "@pyreon/head"
|
|
2
|
+
import { useLoaderData } from "@pyreon/router"
|
|
3
|
+
import type { LoaderContext } from "@pyreon/zero"
|
|
4
|
+
import { Link } from "@pyreon/zero/link"
|
|
5
5
|
|
|
6
6
|
interface Post {
|
|
7
7
|
id: number
|
|
@@ -13,52 +13,52 @@ interface Post {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const POSTS: Record<string, Post> = {
|
|
16
|
-
|
|
16
|
+
"1": {
|
|
17
17
|
id: 1,
|
|
18
|
-
title:
|
|
19
|
-
excerpt:
|
|
18
|
+
title: "Getting Started with Pyreon Zero",
|
|
19
|
+
excerpt: "Learn how to build your first app.",
|
|
20
20
|
body: "Pyreon Zero makes it incredibly easy to build modern web applications. Start by creating a new project with `bun create zero my-app`, then drop your first route file into `src/routes/`. The file-based router automatically picks up new files and generates the route tree for you.\n\nEvery route can export a `loader` for server-side data fetching, a `guard` for navigation protection, and `meta` for SEO metadata. The component itself uses JSX with Pyreon's signal-based reactivity — no virtual DOM overhead, just surgical DOM updates.",
|
|
21
|
-
author:
|
|
22
|
-
date:
|
|
21
|
+
author: "Zero Team",
|
|
22
|
+
date: "2026-03-01",
|
|
23
23
|
},
|
|
24
|
-
|
|
24
|
+
"2": {
|
|
25
25
|
id: 2,
|
|
26
|
-
title:
|
|
27
|
-
excerpt:
|
|
26
|
+
title: "Understanding Signals",
|
|
27
|
+
excerpt: "Deep dive into fine-grained reactivity.",
|
|
28
28
|
body: "Signals are the foundation of Pyreon's reactivity system. Unlike the virtual DOM approach used by React, signals track exactly which DOM nodes depend on which pieces of state. When a signal changes, only the precise text nodes or attributes that reference it are updated.\n\nThis means your app does zero diffing, zero reconciliation, and zero unnecessary re-renders. A counter component that updates a number on screen? Only that one text node is touched. Everything else stays completely untouched.",
|
|
29
|
-
author:
|
|
30
|
-
date:
|
|
29
|
+
author: "Zero Team",
|
|
30
|
+
date: "2026-03-05",
|
|
31
31
|
},
|
|
32
|
-
|
|
32
|
+
"3": {
|
|
33
33
|
id: 3,
|
|
34
|
-
title:
|
|
35
|
-
excerpt:
|
|
36
|
-
body:
|
|
37
|
-
author:
|
|
38
|
-
date:
|
|
34
|
+
title: "Server-Side Rendering Made Simple",
|
|
35
|
+
excerpt: "Pick the right strategy for every page.",
|
|
36
|
+
body: "Zero supports four rendering strategies out of the box: SSR (server-side rendering), SSG (static site generation), ISR (incremental static regeneration), and SPA (single-page application). You can set a default mode in your config and override it per-route.\n\nSSR renders fresh HTML on every request. SSG pre-renders pages at build time. ISR combines both — serving cached static pages while revalidating in the background. SPA skips server rendering entirely for fully client-side pages.",
|
|
37
|
+
author: "Zero Team",
|
|
38
|
+
date: "2026-03-08",
|
|
39
39
|
},
|
|
40
|
-
|
|
40
|
+
"4": {
|
|
41
41
|
id: 4,
|
|
42
|
-
title:
|
|
43
|
-
excerpt:
|
|
42
|
+
title: "Deploying to Production",
|
|
43
|
+
excerpt: "From build to production with any platform.",
|
|
44
44
|
body: "Zero's adapter system makes deployment straightforward. Choose your target platform — Node.js, Bun, Vercel, Cloudflare Workers, or Netlify — and Zero generates the right output for that platform.\n\nThe Node adapter creates a standalone HTTP server. The Bun adapter leverages Bun.serve() for maximum performance. The static adapter exports a fully pre-rendered site ready for any CDN or static hosting platform.",
|
|
45
|
-
author:
|
|
46
|
-
date:
|
|
45
|
+
author: "Zero Team",
|
|
46
|
+
date: "2026-03-10",
|
|
47
47
|
},
|
|
48
|
-
|
|
48
|
+
"5": {
|
|
49
49
|
id: 5,
|
|
50
|
-
title:
|
|
51
|
-
excerpt:
|
|
52
|
-
body:
|
|
53
|
-
author:
|
|
54
|
-
date:
|
|
50
|
+
title: "Optimizing Performance",
|
|
51
|
+
excerpt: "Built-in performance features.",
|
|
52
|
+
body: "Zero includes a comprehensive performance toolkit. The font plugin automatically optimizes Google Fonts with preconnect hints and font-display:swap. The <Image> component provides lazy loading with IntersectionObserver, responsive srcset, and blur-up placeholders.\n\nThe <Link> component prefetches routes on hover or viewport entry, making navigation feel instant. Built-in cache middleware sets optimal Cache-Control headers — immutable caching for hashed assets, stale-while-revalidate for pages.",
|
|
53
|
+
author: "Zero Team",
|
|
54
|
+
date: "2026-03-12",
|
|
55
55
|
},
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export async function loader(ctx: LoaderContext) {
|
|
59
59
|
await new Promise((r) => setTimeout(r, 50))
|
|
60
60
|
const post = POSTS[ctx.params.id]
|
|
61
|
-
if (!post) throw new Error(
|
|
61
|
+
if (!post) throw new Error("Post not found")
|
|
62
62
|
return { post }
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -68,7 +68,7 @@ export default function PostDetail() {
|
|
|
68
68
|
|
|
69
69
|
useHead({
|
|
70
70
|
title: `${post.title} — Pyreon Zero`,
|
|
71
|
-
meta: [{ name:
|
|
71
|
+
meta: [{ name: "description", content: post.excerpt }],
|
|
72
72
|
})
|
|
73
73
|
|
|
74
74
|
return (
|
|
@@ -88,7 +88,7 @@ export default function PostDetail() {
|
|
|
88
88
|
</div>
|
|
89
89
|
|
|
90
90
|
<div class="post-body">
|
|
91
|
-
{post.body.split(
|
|
91
|
+
{post.body.split("\n\n").map((paragraph) => (
|
|
92
92
|
<p style="margin-bottom: var(--space-lg);">{paragraph}</p>
|
|
93
93
|
))}
|
|
94
94
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useHead } from
|
|
2
|
-
import { useLoaderData } from
|
|
3
|
-
import type { LoaderContext } from
|
|
4
|
-
import { Link } from
|
|
1
|
+
import { useHead } from "@pyreon/head"
|
|
2
|
+
import { useLoaderData } from "@pyreon/router"
|
|
3
|
+
import type { LoaderContext } from "@pyreon/zero"
|
|
4
|
+
import { Link } from "@pyreon/zero/link"
|
|
5
5
|
|
|
6
6
|
interface Post {
|
|
7
7
|
id: number
|
|
@@ -12,33 +12,29 @@ interface Post {
|
|
|
12
12
|
const POSTS: Post[] = [
|
|
13
13
|
{
|
|
14
14
|
id: 1,
|
|
15
|
-
title:
|
|
16
|
-
excerpt:
|
|
17
|
-
'Learn how to build your first app with file-based routing, SSR, and signals.',
|
|
15
|
+
title: "Getting Started with Pyreon Zero",
|
|
16
|
+
excerpt: "Learn how to build your first app with file-based routing, SSR, and signals.",
|
|
18
17
|
},
|
|
19
18
|
{
|
|
20
19
|
id: 2,
|
|
21
|
-
title:
|
|
22
|
-
excerpt:
|
|
23
|
-
'Deep dive into fine-grained reactivity — how signals replace the virtual DOM.',
|
|
20
|
+
title: "Understanding Signals",
|
|
21
|
+
excerpt: "Deep dive into fine-grained reactivity — how signals replace the virtual DOM.",
|
|
24
22
|
},
|
|
25
23
|
{
|
|
26
24
|
id: 3,
|
|
27
|
-
title:
|
|
28
|
-
excerpt:
|
|
29
|
-
'SSR, SSG, ISR — pick the right strategy for every page in your app.',
|
|
25
|
+
title: "Server-Side Rendering Made Simple",
|
|
26
|
+
excerpt: "SSR, SSG, ISR — pick the right strategy for every page in your app.",
|
|
30
27
|
},
|
|
31
28
|
{
|
|
32
29
|
id: 4,
|
|
33
|
-
title:
|
|
34
|
-
excerpt:
|
|
35
|
-
'From bun build to production with Node, Bun, Vercel, or Cloudflare adapters.',
|
|
30
|
+
title: "Deploying to Production",
|
|
31
|
+
excerpt: "From bun build to production with Node, Bun, Vercel, or Cloudflare adapters.",
|
|
36
32
|
},
|
|
37
33
|
{
|
|
38
34
|
id: 5,
|
|
39
|
-
title:
|
|
35
|
+
title: "Optimizing Performance",
|
|
40
36
|
excerpt:
|
|
41
|
-
|
|
37
|
+
"Font loading, image optimization, smart caching, and link prefetching out of the box.",
|
|
42
38
|
},
|
|
43
39
|
]
|
|
44
40
|
|
|
@@ -52,10 +48,10 @@ export default function PostsIndex() {
|
|
|
52
48
|
const data = useLoaderData<{ posts: Post[] }>()
|
|
53
49
|
|
|
54
50
|
useHead({
|
|
55
|
-
title:
|
|
51
|
+
title: "Posts — Pyreon Zero",
|
|
56
52
|
meta: [
|
|
57
53
|
{
|
|
58
|
-
name:
|
|
54
|
+
name: "description",
|
|
59
55
|
content: "Example posts showcasing Zero's data loading.",
|
|
60
56
|
},
|
|
61
57
|
],
|
|
@@ -67,25 +63,17 @@ export default function PostsIndex() {
|
|
|
67
63
|
<span class="badge">Data Loading</span>
|
|
68
64
|
<h1 style="margin-top: var(--space-md);">Posts</h1>
|
|
69
65
|
<p>
|
|
70
|
-
Each post is loaded via a <code>loader</code> function — server-side
|
|
71
|
-
|
|
66
|
+
Each post is loaded via a <code>loader</code> function — server-side data fetching that
|
|
67
|
+
runs before the route renders.
|
|
72
68
|
</p>
|
|
73
|
-
<Link
|
|
74
|
-
href="/posts/new"
|
|
75
|
-
class="btn"
|
|
76
|
-
style="margin-top: var(--space-md);"
|
|
77
|
-
>
|
|
69
|
+
<Link href="/posts/new" class="btn" style="margin-top: var(--space-md);">
|
|
78
70
|
+ New Post
|
|
79
71
|
</Link>
|
|
80
72
|
</div>
|
|
81
73
|
|
|
82
74
|
<div class="posts-grid">
|
|
83
75
|
{data.posts.map((post) => (
|
|
84
|
-
<Link
|
|
85
|
-
href={`/posts/${post.id}`}
|
|
86
|
-
class="card post-card"
|
|
87
|
-
prefetch="hover"
|
|
88
|
-
>
|
|
76
|
+
<Link href={`/posts/${post.id}`} class="card post-card" prefetch="hover">
|
|
89
77
|
<span class="post-id">#{post.id}</span>
|
|
90
78
|
<div>
|
|
91
79
|
<h3>{post.title}</h3>
|
|
@@ -95,25 +83,20 @@ export default function PostsIndex() {
|
|
|
95
83
|
))}
|
|
96
84
|
</div>
|
|
97
85
|
|
|
98
|
-
<div
|
|
99
|
-
class="code-block"
|
|
100
|
-
style="max-width: 520px; margin: var(--space-2xl) auto 0;"
|
|
101
|
-
>
|
|
86
|
+
<div class="code-block" style="max-width: 520px; margin: var(--space-2xl) auto 0;">
|
|
102
87
|
<div class="code-block-header">
|
|
103
88
|
<span>posts/index.tsx</span>
|
|
104
89
|
</div>
|
|
105
90
|
<pre>
|
|
106
91
|
<code>
|
|
107
|
-
<span class="cm">{
|
|
108
|
-
<span class="kw">export async function</span>{
|
|
109
|
-
<span class="
|
|
110
|
-
<span class="
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
<span class="
|
|
115
|
-
<span class="kw">const</span> data ={' '}
|
|
116
|
-
<span class="fn">useLoaderData</span>()
|
|
92
|
+
<span class="cm">{"// Export a loader — runs on the server"}</span>
|
|
93
|
+
<span class="kw">export async function</span> <span class="fn">loader</span>(ctx) {"{"}
|
|
94
|
+
<span class="kw">const</span> posts = <span class="kw">await</span> db.posts.
|
|
95
|
+
<span class="fn">findMany</span>()
|
|
96
|
+
<span class="kw">return</span> {"{"} posts {"}"}
|
|
97
|
+
{"}"}
|
|
98
|
+
<span class="cm">{"// Access data in the component"}</span>
|
|
99
|
+
<span class="kw">const</span> data = <span class="fn">useLoaderData</span>()
|
|
117
100
|
</code>
|
|
118
101
|
</pre>
|
|
119
102
|
</div>
|