create-aziz-app 3.1.1 → 3.1.3
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/README.md +658 -207
- package/bin/create-aziz-app.js +302 -351
- package/package.json +1 -1
- package/template/README.md +14 -279
- package/template/_framework/api.js +219 -219
- package/template/_framework/boot.js +33 -33
- package/template/_framework/hooks.js +367 -291
- package/template/_framework/next-response.js +128 -128
- package/template/_framework/route-match.js +145 -145
- package/template/_framework/router.js +465 -259
- package/template/_framework/runtime.js +264 -264
- package/template/app/layout.js +3 -5
- package/template/app/page.js +9 -32
- package/template/docs/01-getting-started.md +151 -151
- package/template/docs/02-project-structure.md +109 -109
- package/template/docs/03-pages-and-layouts.md +140 -140
- package/template/docs/04-routing.md +120 -120
- package/template/docs/05-hooks.md +169 -169
- package/template/docs/06-api-routes.md +210 -210
- package/template/docs/07-api-client.md +151 -151
- package/template/docs/08-styling.md +94 -94
- package/template/docs/09-imports-and-modules.md +97 -97
- package/template/docs/10-database-and-models.md +154 -154
- package/template/docs/11-authentication.md +171 -171
- package/template/docs/12-production-and-deployment.md +121 -121
- package/template/docs/13-internals.md +179 -179
- package/template/docs/README.md +67 -67
- package/template/index.html +20 -20
- package/template/package.json +16 -16
- package/template/scripts/dev-server.js +493 -381
- package/template/scripts/lib/api-router.js +525 -525
- package/template/scripts/lib/js-transform.js +213 -141
- package/template/scripts/lib/module-resolver.js +132 -132
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
# 03 · Pages & Layouts
|
|
2
|
-
|
|
3
|
-
Pages are screens. Layouts wrap screens in shared chrome (nav, footer, providers).
|
|
4
|
-
|
|
5
|
-
## Pages
|
|
6
|
-
|
|
7
|
-
A page is a file named `page.js` (or `page.jsx`) that **exports a default component**.
|
|
8
|
-
|
|
9
|
-
```js
|
|
10
|
-
// app/about/page.js → /about
|
|
11
|
-
export default function About() {
|
|
12
|
-
return (
|
|
13
|
-
<div>
|
|
14
|
-
<h1>About</h1>
|
|
15
|
-
<p>We build lightweight frameworks.</p>
|
|
16
|
-
</div>
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Rules:
|
|
22
|
-
|
|
23
|
-
- The file must be named **`page.js`** inside a folder under `app/`.
|
|
24
|
-
- It must have a **default export**.
|
|
25
|
-
- **JSX works out of the box** — no imports needed.
|
|
26
|
-
|
|
27
|
-
### Pages can use state and hooks
|
|
28
|
-
|
|
29
|
-
```js
|
|
30
|
-
// app/clock/page.js
|
|
31
|
-
export default function Clock() {
|
|
32
|
-
const [now, setNow] = useState(new Date());
|
|
33
|
-
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
const id = setInterval(() => setNow(new Date()), 1000);
|
|
36
|
-
return () => clearInterval(id); // cleanup runs on unmount
|
|
37
|
-
}, []);
|
|
38
|
-
|
|
39
|
-
return <p>{now.toLocaleTimeString()}</p>;
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Hooks are global: `useState`, `useEffect`, `useMemo`, `useRef`, `useReducer`, `useContext`, `useLocalStorage`, `useFetch`, `useApi`, `useRouter`, `useParams`, `useSearchParams`. Full list in [Hooks](./05-hooks.md).
|
|
44
|
-
|
|
45
|
-
### Page metadata
|
|
46
|
-
|
|
47
|
-
Export a `metadata` object to set the document title / description per page:
|
|
48
|
-
|
|
49
|
-
```js
|
|
50
|
-
// app/about/page.js
|
|
51
|
-
export default function About() { /* … */ }
|
|
52
|
-
|
|
53
|
-
export const metadata = {
|
|
54
|
-
title: 'About us',
|
|
55
|
-
description: 'Who we are',
|
|
56
|
-
};
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Layout metadata is merged outermost → innermost, and **page metadata wins** for `title`.
|
|
60
|
-
|
|
61
|
-
## Layouts
|
|
62
|
-
|
|
63
|
-
A layout is a file named `layout.js` that **exports a default component receiving `{ children }`**:
|
|
64
|
-
|
|
65
|
-
```js
|
|
66
|
-
// app/layout.js → wraps EVERY page
|
|
67
|
-
import Navbar from '/components/Navbar.js';
|
|
68
|
-
import Footer from '/components/Footer.js';
|
|
69
|
-
|
|
70
|
-
export default function Layout({ children }) {
|
|
71
|
-
return (
|
|
72
|
-
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
|
73
|
-
<Navbar />
|
|
74
|
-
<main style={{ flex: 1 }}>{children}</main>
|
|
75
|
-
<Footer />
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const metadata = {
|
|
81
|
-
title: 'My App',
|
|
82
|
-
description: 'Built with Aziz.js',
|
|
83
|
-
};
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
`children` is whatever page (or nested layout) the router mounted. Always render it — otherwise nothing below the layout appears.
|
|
87
|
-
|
|
88
|
-
### Nested layouts
|
|
89
|
-
|
|
90
|
-
A `layout.js` inside a folder applies to **that folder and everything under it**, outermost-first:
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
app/layout.js → wraps / (root)
|
|
94
|
-
app/about/layout.js → wraps /about (and children)
|
|
95
|
-
app/blog/layout.js → wraps /blog/*
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
For a request to `/blog/hello`, the chain is:
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
app/layout.js → app/blog/layout.js → app/blog/[slug]/page.js
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Layouts do **not** re-mount when you navigate between sibling routes (their state persists); only the page below them changes.
|
|
105
|
-
|
|
106
|
-
## The 404 page
|
|
107
|
-
|
|
108
|
-
`app/not-found.js` renders when no route matches (or a page exports nothing usable):
|
|
109
|
-
|
|
110
|
-
```js
|
|
111
|
-
// app/not-found.js
|
|
112
|
-
export default function NotFound() {
|
|
113
|
-
return (
|
|
114
|
-
<div style={{ padding: '60px 24px', textAlign: 'center' }}>
|
|
115
|
-
<h1 style={{ fontSize: '72px' }}>404</h1>
|
|
116
|
-
<p>This page could not be found.</p>
|
|
117
|
-
<button onClick={() => navigate('/')}>Go Home</button>
|
|
118
|
-
</div>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
If you don't ship one, a built-in default 404 is used.
|
|
124
|
-
|
|
125
|
-
## Root entry
|
|
126
|
-
|
|
127
|
-
`index.html` is the only real HTML file. It defines `<div id="root">`, loads `/_framework/boot.js`, and the client router renders the matching page into `#root`. You normally **don't touch `index.html`** except to add global `<link>`/`<script>` (the CSS-framework flags inject these for you).
|
|
128
|
-
|
|
129
|
-
## Programmatic navigation from a page
|
|
130
|
-
|
|
131
|
-
```js
|
|
132
|
-
navigate('/about'); // global function
|
|
133
|
-
window.__rerenderPage?.(); // force re-render (advanced)
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
See [Routing](./04-routing.md) for links, params, and search strings.
|
|
137
|
-
|
|
138
|
-
## Continue
|
|
139
|
-
|
|
140
|
-
→ [Routing](./04-routing.md)
|
|
1
|
+
# 03 · Pages & Layouts
|
|
2
|
+
|
|
3
|
+
Pages are screens. Layouts wrap screens in shared chrome (nav, footer, providers).
|
|
4
|
+
|
|
5
|
+
## Pages
|
|
6
|
+
|
|
7
|
+
A page is a file named `page.js` (or `page.jsx`) that **exports a default component**.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// app/about/page.js → /about
|
|
11
|
+
export default function About() {
|
|
12
|
+
return (
|
|
13
|
+
<div>
|
|
14
|
+
<h1>About</h1>
|
|
15
|
+
<p>We build lightweight frameworks.</p>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Rules:
|
|
22
|
+
|
|
23
|
+
- The file must be named **`page.js`** inside a folder under `app/`.
|
|
24
|
+
- It must have a **default export**.
|
|
25
|
+
- **JSX works out of the box** — no imports needed.
|
|
26
|
+
|
|
27
|
+
### Pages can use state and hooks
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
// app/clock/page.js
|
|
31
|
+
export default function Clock() {
|
|
32
|
+
const [now, setNow] = useState(new Date());
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const id = setInterval(() => setNow(new Date()), 1000);
|
|
36
|
+
return () => clearInterval(id); // cleanup runs on unmount
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
return <p>{now.toLocaleTimeString()}</p>;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Hooks are global: `useState`, `useEffect`, `useMemo`, `useRef`, `useReducer`, `useContext`, `useLocalStorage`, `useFetch`, `useApi`, `useRouter`, `useParams`, `useSearchParams`. Full list in [Hooks](./05-hooks.md).
|
|
44
|
+
|
|
45
|
+
### Page metadata
|
|
46
|
+
|
|
47
|
+
Export a `metadata` object to set the document title / description per page:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
// app/about/page.js
|
|
51
|
+
export default function About() { /* … */ }
|
|
52
|
+
|
|
53
|
+
export const metadata = {
|
|
54
|
+
title: 'About us',
|
|
55
|
+
description: 'Who we are',
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Layout metadata is merged outermost → innermost, and **page metadata wins** for `title`.
|
|
60
|
+
|
|
61
|
+
## Layouts
|
|
62
|
+
|
|
63
|
+
A layout is a file named `layout.js` that **exports a default component receiving `{ children }`**:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
// app/layout.js → wraps EVERY page
|
|
67
|
+
import Navbar from '/components/Navbar.js';
|
|
68
|
+
import Footer from '/components/Footer.js';
|
|
69
|
+
|
|
70
|
+
export default function Layout({ children }) {
|
|
71
|
+
return (
|
|
72
|
+
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
|
73
|
+
<Navbar />
|
|
74
|
+
<main style={{ flex: 1 }}>{children}</main>
|
|
75
|
+
<Footer />
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const metadata = {
|
|
81
|
+
title: 'My App',
|
|
82
|
+
description: 'Built with Aziz.js',
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`children` is whatever page (or nested layout) the router mounted. Always render it — otherwise nothing below the layout appears.
|
|
87
|
+
|
|
88
|
+
### Nested layouts
|
|
89
|
+
|
|
90
|
+
A `layout.js` inside a folder applies to **that folder and everything under it**, outermost-first:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
app/layout.js → wraps / (root)
|
|
94
|
+
app/about/layout.js → wraps /about (and children)
|
|
95
|
+
app/blog/layout.js → wraps /blog/*
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For a request to `/blog/hello`, the chain is:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
app/layout.js → app/blog/layout.js → app/blog/[slug]/page.js
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Layouts do **not** re-mount when you navigate between sibling routes (their state persists); only the page below them changes.
|
|
105
|
+
|
|
106
|
+
## The 404 page
|
|
107
|
+
|
|
108
|
+
`app/not-found.js` renders when no route matches (or a page exports nothing usable):
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
// app/not-found.js
|
|
112
|
+
export default function NotFound() {
|
|
113
|
+
return (
|
|
114
|
+
<div style={{ padding: '60px 24px', textAlign: 'center' }}>
|
|
115
|
+
<h1 style={{ fontSize: '72px' }}>404</h1>
|
|
116
|
+
<p>This page could not be found.</p>
|
|
117
|
+
<button onClick={() => navigate('/')}>Go Home</button>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
If you don't ship one, a built-in default 404 is used.
|
|
124
|
+
|
|
125
|
+
## Root entry
|
|
126
|
+
|
|
127
|
+
`index.html` is the only real HTML file. It defines `<div id="root">`, loads `/_framework/boot.js`, and the client router renders the matching page into `#root`. You normally **don't touch `index.html`** except to add global `<link>`/`<script>` (the CSS-framework flags inject these for you).
|
|
128
|
+
|
|
129
|
+
## Programmatic navigation from a page
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
navigate('/about'); // global function
|
|
133
|
+
window.__rerenderPage?.(); // force re-render (advanced)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
See [Routing](./04-routing.md) for links, params, and search strings.
|
|
137
|
+
|
|
138
|
+
## Continue
|
|
139
|
+
|
|
140
|
+
→ [Routing](./04-routing.md)
|
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
# 04 · Routing
|
|
2
|
-
|
|
3
|
-
Aziz.js maps files under `app/` to URLs, the same way the Next.js App Router does.
|
|
4
|
-
|
|
5
|
-
## File → URL table
|
|
6
|
-
|
|
7
|
-
| File | URL |
|
|
8
|
-
|------|-----|
|
|
9
|
-
| `app/page.js` | `/` |
|
|
10
|
-
| `app/about/page.js` | `/about` |
|
|
11
|
-
| `app/blog/page.js` | `/blog` |
|
|
12
|
-
| `app/blog/[slug]/page.js` | `/blog/:slug` |
|
|
13
|
-
| `app/docs/[...path]/page.js` | `/docs/*` (catch-all) |
|
|
14
|
-
| `app/not-found.js` | (renders 404) |
|
|
15
|
-
|
|
16
|
-
The file must be named **`page.js`** — that's what registers a route.
|
|
17
|
-
|
|
18
|
-
## Dynamic segments
|
|
19
|
-
|
|
20
|
-
Create `app/blog/[slug]/page.js`. Brackets become a **parameter**:
|
|
21
|
-
|
|
22
|
-
```js
|
|
23
|
-
// app/blog/[slug]/page.js → /blog/:slug
|
|
24
|
-
export default function Post() {
|
|
25
|
-
const { slug } = useParams();
|
|
26
|
-
return <article>Post: {slug}</article>;
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Visiting `/blog/hello-world` renders `Post: hello-world`.
|
|
31
|
-
|
|
32
|
-
Multiple dynamic segments are fine: `app/shop/[category]/[id]/page.js` → `useParams()` gives `{ category, id }`.
|
|
33
|
-
|
|
34
|
-
## Catch-all segments
|
|
35
|
-
|
|
36
|
-
`[...name]` captures **the rest of the path** as an array:
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
// app/docs/[...path]/page.js → /docs/*
|
|
40
|
-
export default function Docs() {
|
|
41
|
-
const { path } = useParams();
|
|
42
|
-
// /docs/a/b → path === ['a', 'b']
|
|
43
|
-
// /docs/one → path === ['one']
|
|
44
|
-
return <pre>{JSON.stringify(path, null, 2)}</pre>;
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
The literal prefix is validated — `/docs/[...path]` will **not** match `/other/a`.
|
|
49
|
-
|
|
50
|
-
## Route specificity
|
|
51
|
-
|
|
52
|
-
When several routes could match, the most specific wins:
|
|
53
|
-
|
|
54
|
-
1. **Static** (`/blog/new`) beats
|
|
55
|
-
2. **Dynamic** (`/blog/[slug]`) beats
|
|
56
|
-
3. **Catch-all** (`/[...all]`)
|
|
57
|
-
|
|
58
|
-
So with all three present:
|
|
59
|
-
|
|
60
|
-
- `/blog/new` → static route
|
|
61
|
-
- `/blog/hello` → dynamic route
|
|
62
|
-
- `/other/x` → catch-all route
|
|
63
|
-
|
|
64
|
-
Trailing slashes are normalized: `/about/` matches `/about`.
|
|
65
|
-
|
|
66
|
-
## Navigation
|
|
67
|
-
|
|
68
|
-
### `<Link>` (client-side, no full reload)
|
|
69
|
-
|
|
70
|
-
```js
|
|
71
|
-
<Link href="/about">About</Link>
|
|
72
|
-
<Link href="/blog/hello" class="nav-link">Post</Link>
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
`<Link>` intercepts ordinary left-clicks and calls `navigate()`, but **respects** modifier/middle clicks (⌘/Ctrl/Shift/Alt, middle-click) and external/hash/mailto URLs, so the browser behaves normally.
|
|
76
|
-
|
|
77
|
-
### `navigate()`
|
|
78
|
-
|
|
79
|
-
```js
|
|
80
|
-
navigate('/about'); // pushState + render
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### `useRouter()`
|
|
84
|
-
|
|
85
|
-
```js
|
|
86
|
-
const { pathname, push, replace, back, forward, refresh, params } = useRouter();
|
|
87
|
-
|
|
88
|
-
push('/about');
|
|
89
|
-
replace('/search?q=aziz');
|
|
90
|
-
refresh(); // re-render current route
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### `useParams()` / `useSearchParams()`
|
|
94
|
-
|
|
95
|
-
```js
|
|
96
|
-
const { slug } = useParams();
|
|
97
|
-
|
|
98
|
-
const search = useSearchParams();
|
|
99
|
-
const q = search.get('q'); // ?q=aziz → "aziz"
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Browser history
|
|
103
|
-
|
|
104
|
-
The router listens for `popstate`, so Back/Forward work as expected, and scroll position / focus are restored on same-route re-renders.
|
|
105
|
-
|
|
106
|
-
## Custom 404
|
|
107
|
-
|
|
108
|
-
See [Pages & Layouts](./03-pages-and-layouts.md#the-404-page) — export a default component from `app/not-found.js`.
|
|
109
|
-
|
|
110
|
-
## How the router finds pages
|
|
111
|
-
|
|
112
|
-
1. The dev server scans `app/` and emits `/_framework/routes.json` (the **manifest**).
|
|
113
|
-
2. On boot, the client imports that manifest and `import()`s each `page.js` / `layout.js`.
|
|
114
|
-
3. On every navigation it normalizes the path, resolves the most specific route, collects the layout chain, and mounts it into `#root`.
|
|
115
|
-
|
|
116
|
-
You can inspect the manifest live at **<http://localhost:3000/_framework/routes.json>**.
|
|
117
|
-
|
|
118
|
-
## Continue
|
|
119
|
-
|
|
120
|
-
→ [Hooks](./05-hooks.md)
|
|
1
|
+
# 04 · Routing
|
|
2
|
+
|
|
3
|
+
Aziz.js maps files under `app/` to URLs, the same way the Next.js App Router does.
|
|
4
|
+
|
|
5
|
+
## File → URL table
|
|
6
|
+
|
|
7
|
+
| File | URL |
|
|
8
|
+
|------|-----|
|
|
9
|
+
| `app/page.js` | `/` |
|
|
10
|
+
| `app/about/page.js` | `/about` |
|
|
11
|
+
| `app/blog/page.js` | `/blog` |
|
|
12
|
+
| `app/blog/[slug]/page.js` | `/blog/:slug` |
|
|
13
|
+
| `app/docs/[...path]/page.js` | `/docs/*` (catch-all) |
|
|
14
|
+
| `app/not-found.js` | (renders 404) |
|
|
15
|
+
|
|
16
|
+
The file must be named **`page.js`** — that's what registers a route.
|
|
17
|
+
|
|
18
|
+
## Dynamic segments
|
|
19
|
+
|
|
20
|
+
Create `app/blog/[slug]/page.js`. Brackets become a **parameter**:
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
// app/blog/[slug]/page.js → /blog/:slug
|
|
24
|
+
export default function Post() {
|
|
25
|
+
const { slug } = useParams();
|
|
26
|
+
return <article>Post: {slug}</article>;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Visiting `/blog/hello-world` renders `Post: hello-world`.
|
|
31
|
+
|
|
32
|
+
Multiple dynamic segments are fine: `app/shop/[category]/[id]/page.js` → `useParams()` gives `{ category, id }`.
|
|
33
|
+
|
|
34
|
+
## Catch-all segments
|
|
35
|
+
|
|
36
|
+
`[...name]` captures **the rest of the path** as an array:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// app/docs/[...path]/page.js → /docs/*
|
|
40
|
+
export default function Docs() {
|
|
41
|
+
const { path } = useParams();
|
|
42
|
+
// /docs/a/b → path === ['a', 'b']
|
|
43
|
+
// /docs/one → path === ['one']
|
|
44
|
+
return <pre>{JSON.stringify(path, null, 2)}</pre>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The literal prefix is validated — `/docs/[...path]` will **not** match `/other/a`.
|
|
49
|
+
|
|
50
|
+
## Route specificity
|
|
51
|
+
|
|
52
|
+
When several routes could match, the most specific wins:
|
|
53
|
+
|
|
54
|
+
1. **Static** (`/blog/new`) beats
|
|
55
|
+
2. **Dynamic** (`/blog/[slug]`) beats
|
|
56
|
+
3. **Catch-all** (`/[...all]`)
|
|
57
|
+
|
|
58
|
+
So with all three present:
|
|
59
|
+
|
|
60
|
+
- `/blog/new` → static route
|
|
61
|
+
- `/blog/hello` → dynamic route
|
|
62
|
+
- `/other/x` → catch-all route
|
|
63
|
+
|
|
64
|
+
Trailing slashes are normalized: `/about/` matches `/about`.
|
|
65
|
+
|
|
66
|
+
## Navigation
|
|
67
|
+
|
|
68
|
+
### `<Link>` (client-side, no full reload)
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
<Link href="/about">About</Link>
|
|
72
|
+
<Link href="/blog/hello" class="nav-link">Post</Link>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`<Link>` intercepts ordinary left-clicks and calls `navigate()`, but **respects** modifier/middle clicks (⌘/Ctrl/Shift/Alt, middle-click) and external/hash/mailto URLs, so the browser behaves normally.
|
|
76
|
+
|
|
77
|
+
### `navigate()`
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
navigate('/about'); // pushState + render
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### `useRouter()`
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
const { pathname, push, replace, back, forward, refresh, params } = useRouter();
|
|
87
|
+
|
|
88
|
+
push('/about');
|
|
89
|
+
replace('/search?q=aziz');
|
|
90
|
+
refresh(); // re-render current route
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `useParams()` / `useSearchParams()`
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
const { slug } = useParams();
|
|
97
|
+
|
|
98
|
+
const search = useSearchParams();
|
|
99
|
+
const q = search.get('q'); // ?q=aziz → "aziz"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Browser history
|
|
103
|
+
|
|
104
|
+
The router listens for `popstate`, so Back/Forward work as expected, and scroll position / focus are restored on same-route re-renders.
|
|
105
|
+
|
|
106
|
+
## Custom 404
|
|
107
|
+
|
|
108
|
+
See [Pages & Layouts](./03-pages-and-layouts.md#the-404-page) — export a default component from `app/not-found.js`.
|
|
109
|
+
|
|
110
|
+
## How the router finds pages
|
|
111
|
+
|
|
112
|
+
1. The dev server scans `app/` and emits `/_framework/routes.json` (the **manifest**).
|
|
113
|
+
2. On boot, the client imports that manifest and `import()`s each `page.js` / `layout.js`.
|
|
114
|
+
3. On every navigation it normalizes the path, resolves the most specific route, collects the layout chain, and mounts it into `#root`.
|
|
115
|
+
|
|
116
|
+
You can inspect the manifest live at **<http://localhost:3000/_framework/routes.json>**.
|
|
117
|
+
|
|
118
|
+
## Continue
|
|
119
|
+
|
|
120
|
+
→ [Hooks](./05-hooks.md)
|