toiljs 0.0.11 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +3 -1
  2. package/build/cli/.tsbuildinfo +1 -1
  3. package/build/cli/configure.js +10 -4
  4. package/build/cli/create.js +58 -30
  5. package/build/cli/diagnostics.d.ts +55 -0
  6. package/build/cli/diagnostics.js +333 -0
  7. package/build/cli/doctor.d.ts +6 -0
  8. package/build/cli/doctor.js +249 -0
  9. package/build/cli/index.js +26 -0
  10. package/build/cli/proc.d.ts +5 -0
  11. package/build/cli/proc.js +20 -0
  12. package/build/cli/ui.d.ts +1 -0
  13. package/build/cli/ui.js +1 -0
  14. package/build/cli/update.d.ts +7 -0
  15. package/build/cli/update.js +117 -0
  16. package/build/cli/updates.d.ts +10 -0
  17. package/build/cli/updates.js +45 -0
  18. package/build/client/.tsbuildinfo +1 -1
  19. package/build/client/dev/error-overlay.js +1 -1
  20. package/build/client/head/metadata.js +3 -1
  21. package/build/client/index.d.ts +5 -1
  22. package/build/client/index.js +2 -0
  23. package/build/client/navigation/navigation.js +1 -1
  24. package/build/client/routing/Router.js +2 -2
  25. package/build/client/search/search.d.ts +26 -0
  26. package/build/client/search/search.js +101 -0
  27. package/build/client/search/use-page-search.d.ts +8 -0
  28. package/build/client/search/use-page-search.js +21 -0
  29. package/build/compiler/.tsbuildinfo +1 -1
  30. package/build/compiler/generate.js +33 -24
  31. package/build/compiler/index.d.ts +2 -0
  32. package/build/compiler/index.js +1 -0
  33. package/build/compiler/pages.d.ts +8 -0
  34. package/build/compiler/pages.js +37 -0
  35. package/build/compiler/plugin.js +3 -1
  36. package/build/compiler/prerender.d.ts +1 -0
  37. package/build/compiler/prerender.js +11 -5
  38. package/build/compiler/seo.js +10 -3
  39. package/build/io/.tsbuildinfo +1 -1
  40. package/examples/basic/client/components/Header.tsx +43 -41
  41. package/examples/basic/client/components/HoneycombBackground.tsx +223 -230
  42. package/examples/basic/client/public/index.html +18 -16
  43. package/examples/basic/client/routes/(legal)/privacy.tsx +18 -19
  44. package/examples/basic/client/routes/(legal)/terms.tsx +15 -16
  45. package/examples/basic/client/routes/about.tsx +21 -22
  46. package/examples/basic/client/routes/blog/[id].tsx +26 -18
  47. package/examples/basic/client/routes/features/actions.tsx +67 -67
  48. package/examples/basic/client/routes/features/error/index.tsx +27 -27
  49. package/examples/basic/client/routes/features/head.tsx +38 -38
  50. package/examples/basic/client/routes/features/index.tsx +83 -75
  51. package/examples/basic/client/routes/features/realtime.tsx +34 -32
  52. package/examples/basic/client/routes/features/script.tsx +31 -31
  53. package/examples/basic/client/routes/features/seo.tsx +39 -39
  54. package/examples/basic/client/routes/features/template/index.tsx +20 -20
  55. package/examples/basic/client/routes/features/template/template.tsx +16 -18
  56. package/examples/basic/client/routes/gallery/@modal/(.)photo/[id].tsx +23 -23
  57. package/examples/basic/client/routes/gallery/index.tsx +42 -42
  58. package/examples/basic/client/routes/gallery/photo/[id].tsx +18 -18
  59. package/examples/basic/client/routes/get-started.tsx +157 -84
  60. package/examples/basic/client/routes/index.tsx +137 -96
  61. package/examples/basic/client/routes/loader-demo/index.tsx +59 -52
  62. package/examples/basic/client/routes/search.tsx +61 -0
  63. package/examples/basic/client/routes/test.tsx +7 -8
  64. package/examples/basic/client/styles/main.css +624 -552
  65. package/package.json +2 -2
  66. package/presets/eslint.js +10 -3
  67. package/src/cli/configure.ts +363 -353
  68. package/src/cli/create.ts +563 -530
  69. package/src/cli/diagnostics.ts +421 -0
  70. package/src/cli/doctor.ts +318 -0
  71. package/src/cli/features.ts +166 -160
  72. package/src/cli/index.ts +242 -211
  73. package/src/cli/proc.ts +30 -0
  74. package/src/cli/ui.ts +111 -103
  75. package/src/cli/update.ts +150 -0
  76. package/src/cli/updates.ts +69 -0
  77. package/src/client/components/Image.tsx +91 -89
  78. package/src/client/dev/error-overlay.tsx +193 -197
  79. package/src/client/head/metadata.ts +94 -92
  80. package/src/client/index.ts +79 -64
  81. package/src/client/navigation/Link.tsx +94 -100
  82. package/src/client/navigation/navigation.ts +215 -218
  83. package/src/client/routing/Router.tsx +210 -193
  84. package/src/client/routing/hooks.ts +110 -114
  85. package/src/client/routing/lazy.ts +77 -81
  86. package/src/client/search/search.ts +189 -0
  87. package/src/client/search/use-page-search.ts +73 -0
  88. package/src/compiler/config.ts +173 -171
  89. package/src/compiler/fonts.ts +89 -87
  90. package/src/compiler/generate.ts +45 -27
  91. package/src/compiler/image-report.ts +88 -85
  92. package/src/compiler/index.ts +2 -0
  93. package/src/compiler/pages.ts +70 -0
  94. package/src/compiler/plugin.ts +51 -47
  95. package/src/compiler/prerender.ts +152 -130
  96. package/src/compiler/routes.ts +132 -131
  97. package/src/compiler/seo.ts +381 -356
  98. package/src/compiler/vite.ts +155 -145
  99. package/src/io/FastSet.ts +99 -96
  100. package/test/configure.test.ts +94 -90
  101. package/test/doctor.test.ts +140 -0
  102. package/test/dom/Image.test.tsx +73 -46
  103. package/test/dom/Script.test.tsx +48 -45
  104. package/test/dom/action.test.tsx +146 -129
  105. package/test/dom/error-overlay.test.tsx +1 -1
  106. package/test/dom/loader.test.tsx +2 -2
  107. package/test/dom/revalidate.test.tsx +1 -1
  108. package/test/dom/route-head.test.tsx +1 -2
  109. package/test/dom/router-loading.test.tsx +1 -1
  110. package/test/dom/slot.test.tsx +131 -109
  111. package/test/dom/view-transitions.test.tsx +53 -51
  112. package/test/features.test.ts +149 -142
  113. package/test/fonts.test.ts +28 -26
  114. package/test/head.test.ts +45 -35
  115. package/test/metadata.test.ts +42 -41
  116. package/test/pages.test.ts +105 -0
  117. package/test/prerender.test.ts +54 -46
  118. package/test/search.test.ts +114 -0
  119. package/test/seo.test.ts +30 -8
  120. package/test/update.test.ts +44 -0
@@ -1,230 +1,223 @@
1
- import { useRef, useEffect } from 'react';
2
-
3
- const HEX_R = 34;
4
- const GAP = 3;
5
- const DRAW_R = HEX_R - GAP;
6
- const GLOW_DIST = 140;
7
- const LOGO_SRC = '/images/logo.svg';
8
-
9
- function tracePath(ctx: CanvasRenderingContext2D, cx: number, cy: number, r: number) {
10
- ctx.beginPath();
11
-
12
- for (let i = 0; i < 6; i++) {
13
- const a = (Math.PI / 3) * i - Math.PI / 6;
14
- const x = cx + r * Math.cos(a);
15
- const y = cy + r * Math.sin(a);
16
-
17
- if (i === 0) {
18
- ctx.moveTo(x, y);
19
- } else {
20
- ctx.lineTo(x, y);
21
- }
22
- }
23
-
24
- ctx.closePath();
25
- }
26
-
27
- function buildGrid(w: number, h: number): Array<{ x: number; y: number }> {
28
- const colW = Math.sqrt(3) * HEX_R;
29
-
30
- const rowH = HEX_R * 1.5;
31
- const cols = Math.ceil(w / colW) + 2;
32
- const rows = Math.ceil(h / rowH) + 2;
33
- const hexes: Array<{ x: number; y: number }> = [];
34
-
35
- for (let row = -1; row < rows; row++) {
36
- for (let col = -1; col < cols; col++) {
37
- hexes.push({
38
- x: col * colW + (row % 2 !== 0 ? colW / 2 : 0),
39
- y: row * rowH,
40
- });
41
- }
42
- }
43
-
44
- return hexes;
45
- }
46
-
47
- /** Samples logo colours per hex centre for use in border glow. */
48
- function buildLogoColors(
49
- img: HTMLImageElement,
50
- hexes: Array<{ x: number; y: number }>,
51
- w: number,
52
- h: number,
53
- ): Array<[number, number, number]> | null {
54
- const lc = document.createElement('canvas');
55
-
56
- lc.width = w;
57
- lc.height = h;
58
-
59
- const lctx = lc.getContext('2d');
60
-
61
- if (!lctx) return null;
62
-
63
- // Draw logo large + blurred, roughly where the hero logo sits in the viewport
64
- const size = 700;
65
- const cx = w / 2;
66
- const cy = h * 0.42;
67
-
68
- lctx.filter = 'blur(90px)';
69
- lctx.drawImage(img, cx - size / 2, cy - size / 2, size, size);
70
- lctx.filter = 'none';
71
-
72
- // Sample one pixel per hex centre so we can use logo colours for border glow
73
- return hexes.map(({ x, y }) => {
74
- const px = Math.round(Math.max(0, Math.min(w - 1, x)));
75
- const py = Math.round(Math.max(0, Math.min(h - 1, y)));
76
- const d = lctx.getImageData(px, py, 1, 1).data;
77
-
78
- return [d[0], d[1], d[2]] as [number, number, number];
79
- });
80
- }
81
-
82
- export default function HoneycombBackground() {
83
- const canvasRef = useRef<HTMLCanvasElement>(null);
84
- const mouse = useRef({ x: -9999, y: -9999 });
85
-
86
- useEffect(() => {
87
- const canvas = canvasRef.current;
88
-
89
- if (!canvas) return;
90
-
91
- const ctx = canvas.getContext('2d');
92
-
93
- if (!ctx) return;
94
-
95
- const dpr = window.devicePixelRatio || 1;
96
- let hexes: Array<{ x: number; y: number }> = [];
97
- let hexColors: Array<[number, number, number]> = [];
98
- let raf: number;
99
-
100
- const img = new Image();
101
-
102
- img.onload = () => {
103
- const colors = buildLogoColors(img, hexes, window.innerWidth, window.innerHeight);
104
-
105
- if (colors) {
106
- hexColors = colors;
107
- }
108
- };
109
-
110
- img.src = LOGO_SRC;
111
-
112
- function resize() {
113
- if (!canvas || !ctx) return;
114
-
115
- const w = window.innerWidth;
116
- const h = window.innerHeight;
117
-
118
- canvas.width = w * dpr;
119
- canvas.height = h * dpr;
120
- canvas.style.width = `${w}px`;
121
- canvas.style.height = `${h}px`;
122
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
123
- hexes = buildGrid(w, h);
124
-
125
- // Rebuild logo colours if image is already loaded
126
- if (img.complete && img.naturalWidth > 0) {
127
- const colors = buildLogoColors(img, hexes, w, h);
128
-
129
- if (colors) {
130
- hexColors = colors;
131
- }
132
- }
133
- }
134
-
135
- function draw() {
136
- if (!ctx) return;
137
-
138
- const w = window.innerWidth;
139
- const h = window.innerHeight;
140
-
141
- ctx.clearRect(0, 0, w, h);
142
-
143
- const mx = mouse.current.x;
144
- const my = mouse.current.y;
145
-
146
- for (let i = 0; i < hexes.length; i++) {
147
- const hex = hexes[i];
148
-
149
- if (!hex) continue;
150
-
151
- const { x, y } = hex;
152
- const dist = Math.hypot(x - mx, y - my);
153
- const t = Math.max(0, 1 - dist / GLOW_DIST);
154
- const ease = t * t * (3 - 2 * t);
155
-
156
- // Base fill
157
- tracePath(ctx, x, y, DRAW_R);
158
- ctx.fillStyle = 'rgba(255,255,255,0.018)';
159
- ctx.fill();
160
-
161
-
162
-
163
- // Base border
164
- tracePath(ctx, x, y, DRAW_R);
165
- ctx.strokeStyle = 'rgba(255,255,255,0.055)';
166
- ctx.lineWidth = 1;
167
- ctx.stroke();
168
-
169
- // Glow border using logo-sampled colour
170
- if (ease > 0) {
171
- const col = hexColors[i];
172
- const r = col ? col[0] : 120;
173
- const g = col ? col[1] : 180;
174
- const b = col ? col[2] : 255;
175
-
176
- // If the logo has colour here, use it; otherwise fall back to a soft white
177
- const bright = r + g + b;
178
- const fr = bright > 30 ? r : 120;
179
- const fg = bright > 30 ? g : 180;
180
- const fb = bright > 30 ? b : 255;
181
-
182
- ctx.save();
183
- ctx.shadowColor = `rgba(${fr},${fg},${fb},${ease * 0.25})`;
184
- ctx.shadowBlur = 8 * ease;
185
- ctx.strokeStyle = `rgba(${fr},${fg},${fb},${ease * 0.18})`;
186
- ctx.lineWidth = 1 + ease * 0.5;
187
- ctx.stroke();
188
- ctx.restore();
189
- }
190
- }
191
-
192
- raf = requestAnimationFrame(draw);
193
- }
194
-
195
- resize();
196
- draw();
197
-
198
- const onResize = () => {
199
- cancelAnimationFrame(raf);
200
- resize();
201
- draw();
202
- };
203
-
204
- const onMove = (e: MouseEvent) => {
205
- mouse.current = { x: e.clientX, y: e.clientY };
206
- };
207
-
208
- const onLeave = () => {
209
- mouse.current = { x: -9999, y: -9999 };
210
- };
211
-
212
- window.addEventListener('resize', onResize);
213
- window.addEventListener('mousemove', onMove);
214
- document.addEventListener('mouseleave', onLeave);
215
-
216
- return () => {
217
- cancelAnimationFrame(raf);
218
- window.removeEventListener('resize', onResize);
219
- window.removeEventListener('mousemove', onMove);
220
- document.removeEventListener('mouseleave', onLeave);
221
- };
222
- }, []);
223
-
224
- return (
225
- <canvas
226
- ref={canvasRef}
227
- className="honeycomb-canvas"
228
- />
229
- );
230
- }
1
+ import { useRef, useEffect } from 'react';
2
+
3
+ const HEX_R = 34;
4
+ const GAP = 3;
5
+ const DRAW_R = HEX_R - GAP;
6
+ const GLOW_DIST = 140;
7
+ const LOGO_SRC = '/images/logo.svg';
8
+
9
+ function tracePath(ctx: CanvasRenderingContext2D, cx: number, cy: number, r: number) {
10
+ ctx.beginPath();
11
+
12
+ for (let i = 0; i < 6; i++) {
13
+ const a = (Math.PI / 3) * i - Math.PI / 6;
14
+ const x = cx + r * Math.cos(a);
15
+ const y = cy + r * Math.sin(a);
16
+
17
+ if (i === 0) {
18
+ ctx.moveTo(x, y);
19
+ } else {
20
+ ctx.lineTo(x, y);
21
+ }
22
+ }
23
+
24
+ ctx.closePath();
25
+ }
26
+
27
+ function buildGrid(w: number, h: number): Array<{ x: number; y: number }> {
28
+ const colW = Math.sqrt(3) * HEX_R;
29
+
30
+ const rowH = HEX_R * 1.5;
31
+ const cols = Math.ceil(w / colW) + 2;
32
+ const rows = Math.ceil(h / rowH) + 2;
33
+ const hexes: Array<{ x: number; y: number }> = [];
34
+
35
+ for (let row = -1; row < rows; row++) {
36
+ for (let col = -1; col < cols; col++) {
37
+ hexes.push({
38
+ x: col * colW + (row % 2 !== 0 ? colW / 2 : 0),
39
+ y: row * rowH
40
+ });
41
+ }
42
+ }
43
+
44
+ return hexes;
45
+ }
46
+
47
+ /** Samples logo colours per hex centre for use in border glow. */
48
+ function buildLogoColors(
49
+ img: HTMLImageElement,
50
+ hexes: Array<{ x: number; y: number }>,
51
+ w: number,
52
+ h: number
53
+ ): Array<[number, number, number]> | null {
54
+ const lc = document.createElement('canvas');
55
+
56
+ lc.width = w;
57
+ lc.height = h;
58
+
59
+ const lctx = lc.getContext('2d');
60
+
61
+ if (!lctx) return null;
62
+
63
+ // Draw logo large + blurred, roughly where the hero logo sits in the viewport
64
+ const size = 700;
65
+ const cx = w / 2;
66
+ const cy = h * 0.42;
67
+
68
+ lctx.filter = 'blur(90px)';
69
+ lctx.drawImage(img, cx - size / 2, cy - size / 2, size, size);
70
+ lctx.filter = 'none';
71
+
72
+ // Sample one pixel per hex centre so we can use logo colours for border glow
73
+ return hexes.map(({ x, y }) => {
74
+ const px = Math.round(Math.max(0, Math.min(w - 1, x)));
75
+ const py = Math.round(Math.max(0, Math.min(h - 1, y)));
76
+ const d = lctx.getImageData(px, py, 1, 1).data;
77
+
78
+ return [d[0], d[1], d[2]] as [number, number, number];
79
+ });
80
+ }
81
+
82
+ export default function HoneycombBackground() {
83
+ const canvasRef = useRef<HTMLCanvasElement>(null);
84
+ const mouse = useRef({ x: -9999, y: -9999 });
85
+
86
+ useEffect(() => {
87
+ const canvas = canvasRef.current;
88
+
89
+ if (!canvas) return;
90
+
91
+ const ctx = canvas.getContext('2d');
92
+
93
+ if (!ctx) return;
94
+
95
+ const dpr = window.devicePixelRatio || 1;
96
+ let hexes: Array<{ x: number; y: number }> = [];
97
+ let hexColors: Array<[number, number, number]> = [];
98
+ let raf: number;
99
+
100
+ const img = new Image();
101
+
102
+ img.onload = () => {
103
+ const colors = buildLogoColors(img, hexes, window.innerWidth, window.innerHeight);
104
+
105
+ if (colors) {
106
+ hexColors = colors;
107
+ }
108
+ };
109
+
110
+ img.src = LOGO_SRC;
111
+
112
+ function resize() {
113
+ if (!canvas || !ctx) return;
114
+
115
+ const w = window.innerWidth;
116
+ const h = window.innerHeight;
117
+
118
+ canvas.width = w * dpr;
119
+ canvas.height = h * dpr;
120
+ canvas.style.width = `${w}px`;
121
+ canvas.style.height = `${h}px`;
122
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
123
+ hexes = buildGrid(w, h);
124
+
125
+ // Rebuild logo colours if image is already loaded
126
+ if (img.complete && img.naturalWidth > 0) {
127
+ const colors = buildLogoColors(img, hexes, w, h);
128
+
129
+ if (colors) {
130
+ hexColors = colors;
131
+ }
132
+ }
133
+ }
134
+
135
+ function draw() {
136
+ if (!ctx) return;
137
+
138
+ const w = window.innerWidth;
139
+ const h = window.innerHeight;
140
+
141
+ ctx.clearRect(0, 0, w, h);
142
+
143
+ const mx = mouse.current.x;
144
+ const my = mouse.current.y;
145
+
146
+ for (let i = 0; i < hexes.length; i++) {
147
+ const hex = hexes[i];
148
+
149
+ if (!hex) continue;
150
+
151
+ const { x, y } = hex;
152
+ const dist = Math.hypot(x - mx, y - my);
153
+ const t = Math.max(0, 1 - dist / GLOW_DIST);
154
+ const ease = t * t * (3 - 2 * t);
155
+
156
+ // Base fill
157
+ tracePath(ctx, x, y, DRAW_R);
158
+ ctx.fillStyle = 'rgba(255,255,255,0.018)';
159
+ ctx.fill();
160
+
161
+ // Base border
162
+ tracePath(ctx, x, y, DRAW_R);
163
+ ctx.strokeStyle = 'rgba(255,255,255,0.055)';
164
+ ctx.lineWidth = 1;
165
+ ctx.stroke();
166
+
167
+ // Glow border using logo-sampled colour
168
+ if (ease > 0) {
169
+ const col = hexColors[i];
170
+ const r = col ? col[0] : 120;
171
+ const g = col ? col[1] : 180;
172
+ const b = col ? col[2] : 255;
173
+
174
+ // If the logo has colour here, use it; otherwise fall back to a soft white
175
+ const bright = r + g + b;
176
+ const fr = bright > 30 ? r : 120;
177
+ const fg = bright > 30 ? g : 180;
178
+ const fb = bright > 30 ? b : 255;
179
+
180
+ ctx.save();
181
+ ctx.shadowColor = `rgba(${fr},${fg},${fb},${ease * 0.25})`;
182
+ ctx.shadowBlur = 8 * ease;
183
+ ctx.strokeStyle = `rgba(${fr},${fg},${fb},${ease * 0.18})`;
184
+ ctx.lineWidth = 1 + ease * 0.5;
185
+ ctx.stroke();
186
+ ctx.restore();
187
+ }
188
+ }
189
+
190
+ raf = requestAnimationFrame(draw);
191
+ }
192
+
193
+ resize();
194
+ draw();
195
+
196
+ const onResize = () => {
197
+ cancelAnimationFrame(raf);
198
+ resize();
199
+ draw();
200
+ };
201
+
202
+ const onMove = (e: MouseEvent) => {
203
+ mouse.current = { x: e.clientX, y: e.clientY };
204
+ };
205
+
206
+ const onLeave = () => {
207
+ mouse.current = { x: -9999, y: -9999 };
208
+ };
209
+
210
+ window.addEventListener('resize', onResize);
211
+ window.addEventListener('mousemove', onMove);
212
+ document.addEventListener('mouseleave', onLeave);
213
+
214
+ return () => {
215
+ cancelAnimationFrame(raf);
216
+ window.removeEventListener('resize', onResize);
217
+ window.removeEventListener('mousemove', onMove);
218
+ document.removeEventListener('mouseleave', onLeave);
219
+ };
220
+ }, []);
221
+
222
+ return <canvas ref={canvasRef} className="honeycomb-canvas" />;
223
+ }
@@ -1,16 +1,18 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <meta name="theme-color" content="#080D11" />
7
- <link rel="icon" type="image/x-icon" href="/favicon.ico" />
8
- <title>ToilJS</title>
9
- <link rel="preconnect" href="https://fonts.googleapis.com" />
10
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
- <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@700;800;900&display=swap" rel="stylesheet" />
12
- </head>
13
- <body>
14
- <div id="root"></div>
15
- </body>
16
- </html>
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="theme-color" content="#080D11" />
7
+ <link rel="icon" type="image/x-icon" href="/favicon.ico" />
8
+ <title>ToilJS</title>
9
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
+ <link
12
+ href="https://fonts.googleapis.com/css2?family=Montserrat:wght@700;800;900&display=swap"
13
+ rel="stylesheet" />
14
+ </head>
15
+ <body>
16
+ <div id="root"></div>
17
+ </body>
18
+ </html>
@@ -1,19 +1,18 @@
1
- export const metadata: Toil.Metadata = { title: 'Privacy' };
2
-
3
- // Route group: the `(legal)` folder organizes files without adding a URL segment, so this route is
4
- // served at `/privacy`, not `/legal/privacy`. Groups can carry their own layout too.
5
- export default function Privacy() {
6
- return (
7
- <main>
8
- <h1>Privacy</h1>
9
- <p>
10
- Served at <code>/privacy</code> from <code>routes/(legal)/privacy.tsx</code>. The
11
- <code> (legal)</code> group adds no URL segment.
12
- </p>
13
- <p>
14
- <Toil.Link href="/terms">Terms</Toil.Link>{' '}
15
- <Toil.Link href="/features">Back to features</Toil.Link>
16
- </p>
17
- </main>
18
- );
19
- }
1
+ export const metadata: Toil.Metadata = { title: 'Privacy' };
2
+
3
+ // Route group: the `(legal)` folder organizes files without adding a URL segment, so this route is
4
+ // served at `/privacy`, not `/legal/privacy`. Groups can carry their own layout too.
5
+ export default function Privacy() {
6
+ return (
7
+ <main>
8
+ <h1>Privacy</h1>
9
+ <p>
10
+ Served at <code>/privacy</code> from <code>routes/(legal)/privacy.tsx</code>. The
11
+ <code> (legal)</code> group adds no URL segment.
12
+ </p>
13
+ <p>
14
+ <Toil.Link href="/terms">Terms</Toil.Link> <Toil.Link href="/features">Back to features</Toil.Link>
15
+ </p>
16
+ </main>
17
+ );
18
+ }
@@ -1,16 +1,15 @@
1
- export const metadata: Toil.Metadata = { title: 'Terms' };
2
-
3
- export default function Terms() {
4
- return (
5
- <main>
6
- <h1>Terms</h1>
7
- <p>
8
- Also in the <code>(legal)</code> group, served at <code>/terms</code>.
9
- </p>
10
- <p>
11
- <Toil.Link href="/privacy">Privacy</Toil.Link>{' '}
12
- <Toil.Link href="/features">Back to features</Toil.Link>
13
- </p>
14
- </main>
15
- );
16
- }
1
+ export const metadata: Toil.Metadata = { title: 'Terms' };
2
+
3
+ export default function Terms() {
4
+ return (
5
+ <main>
6
+ <h1>Terms</h1>
7
+ <p>
8
+ Also in the <code>(legal)</code> group, served at <code>/terms</code>.
9
+ </p>
10
+ <p>
11
+ <Toil.Link href="/privacy">Privacy</Toil.Link> <Toil.Link href="/features">Back to features</Toil.Link>
12
+ </p>
13
+ </main>
14
+ );
15
+ }
@@ -1,22 +1,21 @@
1
- // Declarative per-route SEO, resolved by the router into <title> + <meta>/<link> tags, and baked
2
- // into static HTML at build (see build/client/about/index.html). The layout's titleTemplate wraps
3
- // this title, so the tab reads "About | ToilJS"; component-level useHead/<Head> can override.
4
- export const metadata: Toil.Metadata = {
5
- title: 'About',
6
- description: 'About the ToilJS example app.',
7
- openGraph: { title: 'About, ToilJS', type: 'website' },
8
- };
9
-
10
- export default function About() {
11
- return (
12
- <main>
13
- <h1>About</h1>
14
- <p>
15
- This page is served by <code>client/routes/about.tsx</code>. Its tab title comes from
16
- the <code>metadata</code> export above, wrapped by the layout template into{' '}
17
- <code>About | ToilJS</code>.
18
- </p>
19
- <Toil.Link href="/features">See every feature</Toil.Link>
20
- </main>
21
- );
22
- }
1
+ // Declarative per-route SEO, resolved by the router into <title> + <meta>/<link> tags, and baked
2
+ // into static HTML at build (see build/client/about/index.html). The layout's titleTemplate wraps
3
+ // this title, so the tab reads "About | ToilJS"; component-level useHead/<Head> can override.
4
+ export const metadata: Toil.Metadata = {
5
+ title: 'About',
6
+ description: 'About the ToilJS example app.',
7
+ openGraph: { title: 'About, ToilJS', type: 'website' }
8
+ };
9
+
10
+ export default function About() {
11
+ return (
12
+ <main>
13
+ <h1>About</h1>
14
+ <p>
15
+ This page is served by <code>client/routes/about.tsx</code>. Its tab title comes from the{' '}
16
+ <code>metadata</code> export above, wrapped by the layout template into <code>About | ToilJS</code>.
17
+ </p>
18
+ <Toil.Link href="/features">See every feature</Toil.Link>
19
+ </main>
20
+ );
21
+ }
@@ -1,18 +1,26 @@
1
- // Dynamic metadata from the route param, so the tab reads "Blog post 42 | ToilJS".
2
- export const generateMetadata: Toil.GenerateMetadata = ({ params }) => ({
3
- title: `Blog post ${params.id}`,
4
- description: `Reading blog post ${params.id}.`,
5
- });
6
-
7
- export default function BlogPost() {
8
- const { id } = Toil.useParams();
9
- return (
10
- <main>
11
- <h1>Blog post {id}</h1>
12
- <p>
13
- Dynamic route from <code>client/routes/blog/[id].tsx</code>.
14
- </p>
15
- <Toil.Link href="/features">See every feature</Toil.Link>
16
- </main>
17
- );
18
- }
1
+ // Dynamic metadata from the route param, so the tab reads "Blog post 42 | ToilJS".
2
+ export const generateMetadata: Toil.GenerateMetadata = ({ params }) => ({
3
+ title: `Blog post ${params.id}`,
4
+ description: `Reading blog post ${params.id}.`
5
+ });
6
+
7
+ // The per-post title above is dynamic, so it can't be statically indexed for site search. These
8
+ // static hints make the blog discoverable from the /search page anyway (see usePageSearch).
9
+ export const searchHints: Toil.SearchHints = {
10
+ title: 'Blog',
11
+ description: 'Articles and updates from the ToilJS example app.',
12
+ keywords: ['blog', 'posts', 'articles']
13
+ };
14
+
15
+ export default function BlogPost() {
16
+ const { id } = Toil.useParams();
17
+ return (
18
+ <main>
19
+ <h1>Blog post {id}</h1>
20
+ <p>
21
+ Dynamic route from <code>client/routes/blog/[id].tsx</code>.
22
+ </p>
23
+ <Toil.Link href="/features">See every feature</Toil.Link>
24
+ </main>
25
+ );
26
+ }