@wular/pnext 0.0.6 → 0.0.8

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/reference/env.md CHANGED
@@ -1,29 +1,29 @@
1
1
  # Environment Variables
2
2
 
3
- pnext loads `.env*` files from the project root, not from `src/`.
3
+ pnext loads `.env*` files from the project root, never from `src/`. Variables are server-only unless their name marks them for the browser. Server Components, layouts, route handlers, and the proxy read them from `process.env`.
4
4
 
5
- Variables already set on `process.env` win. Missing variables are loaded in this order:
5
+ ## What reaches the browser
6
6
 
7
- 1. `.env.$NODE_ENV.local`
8
- 2. `.env.local` except when `NODE_ENV` is `test`
9
- 3. `.env.$NODE_ENV`
10
- 4. `.env`
11
-
12
- If `NODE_ENV` is not set, `pnext dev` uses `development`; other commands use `production`.
13
-
14
- ## Browser Variables
15
-
16
- Browser bundles inline only:
7
+ Browser bundles inline only these:
17
8
 
18
9
  - `process.env.NODE_ENV`
19
10
  - `process.env.NEXT_PUBLIC_*`
20
11
  - `process.env.PNEXT_PUBLIC_*`
21
12
 
22
- Other variables stay server-only. Server Components, layouts, route handlers, and proxy can read them through `process.env`.
13
+ ## Load order
14
+
15
+ Variables already set on `process.env` win. Missing ones are loaded from:
23
16
 
24
- ## Variable References
17
+ 1. `.env.$NODE_ENV.local`
18
+ 2. `.env.local`, except when `NODE_ENV` is `test`
19
+ 3. `.env.$NODE_ENV`
20
+ 4. `.env`
21
+
22
+ If `NODE_ENV` is not set, the dev server uses `development` and every other command uses `production`.
25
23
 
26
- Values can reference earlier environment variables:
24
+ ## Variable references
25
+
26
+ A value can reference an earlier variable, with or without braces, and with an optional fallback.
27
27
 
28
28
  ```env
29
29
  API_HOST=api.example.com
@@ -31,10 +31,8 @@ NEXT_PUBLIC_API_URL=https://$API_HOST
31
31
  PORT=${APP_PORT:-3000}
32
32
  ```
33
33
 
34
- `${VAR}` and `${VAR:-fallback}` both work. A variable already on `process.env` wins over the file's own value.
34
+ Escape the dollar sign when it should be literal, as `\$10`.
35
35
 
36
- Escape `$` when it should be literal:
36
+ ## PNEXT_COMPAT
37
37
 
38
- ```env
39
- PRICE=\$10
40
- ```
38
+ Setting it to `next` gives an app with no `pnext.config.ts` the `next/*` module surface and `next.config.js` support. When a config file is present, it forces Next compatibility on and leaves every other compat flag as written. It is read after the `.env*` files load, so a `.env` file can set it. See [Compatibility](./compat.md).
@@ -0,0 +1,132 @@
1
+ # Getting Started
2
+
3
+ pnext is a Preact framework for file-routed apps. It renders on the server by default and ships client JavaScript only where a component opts in.
4
+
5
+ > pnext requires [Bun](https://bun.sh/get) - make sure it is installed first.
6
+
7
+ <!-- tabs:start -->
8
+
9
+ ## New app
10
+
11
+ ```sh
12
+ bunx @wular/pnext create my-app
13
+ cd my-app
14
+ bun dev
15
+ ```
16
+
17
+ The app is running at `http://localhost:3000`. Edit `app/page.tsx`; the browser reloads on save.
18
+
19
+ `app/page.tsx` is a Server Component: it can be async, load data on the server, and ships no client JavaScript. The counter it renders, `app/counter.tsx`, starts with `'use client'`, so it alone hydrates in the browser. That's the model: files name routes, the server renders by default, components opt in to the client.
20
+
21
+ ## Coming from Next.js
22
+
23
+ Run in your Next project:
24
+
25
+ ```sh
26
+ bunx @wular/pnext migrate
27
+ ```
28
+
29
+ It converts the project to pnext with `compat.next` enabled, without editing app source. `--dry-run` previews.
30
+
31
+ With `compat.next`, the `next/*` modules, `next.config.js`, server actions, metadata, and the rest of the App Router behavior work as they do in Next, validated against Next's own test suite. [Compatibility](./compat.md) covers the full surface and where it stops short.
32
+
33
+ <!-- tabs:end -->
34
+
35
+ ## Build and deploy
36
+
37
+ ```sh
38
+ pnext build
39
+ pnext start
40
+ ```
41
+
42
+ `pnext build` makes the production build and `pnext start` serves it. Routes render on the server per request; ones that never read the request are prerendered to static HTML at build time.
43
+
44
+ That deploys anywhere Bun runs. For Vercel, build with `pnext build --adapter vercel` and it writes ready-to-deploy Build Output.
45
+
46
+ ## A quick tour
47
+
48
+ ### Your first page
49
+
50
+ Routes live in `app/`. A `page.tsx` is a Server Component by default. It runs only on the server, so it can be async and talk to your database, filesystem, or internal services directly. None of that code reaches the browser:
51
+
52
+ ```tsx
53
+ // app/posts/[id]/page.tsx
54
+ import type { PageProps } from '#gen/app/posts/[id]/page'
55
+
56
+ export default async function Page({ params }: PageProps) {
57
+ const { id } = await params
58
+ const post = await db.post.findUnique({ where: { id } })
59
+ return (
60
+ <article>
61
+ <h1>{post.title}</h1>
62
+ <p>{post.body}</p>
63
+ </article>
64
+ )
65
+ }
66
+ ```
67
+
68
+ This page ships **0 KB** of JavaScript. Layouts work the same way: the root `layout.tsx` owns `<html>` and `<body>` and can export `metadata`.
69
+
70
+ ### Adding interactivity
71
+
72
+ Mark a component with `"use client"` where you need state, effects, or event handlers. Server Components render it into the page, and only that island hydrates in the browser:
73
+
74
+ ```tsx
75
+ // app/counter.tsx
76
+ 'use client'
77
+ import { useState } from 'preact/hooks'
78
+
79
+ export function Counter({ initial }: { initial: number }) {
80
+ const [count, setCount] = useState(initial)
81
+ return (
82
+ <button onClick={() => setCount(count + 1)}>Count {count}</button>
83
+ )
84
+ }
85
+ ```
86
+
87
+ Client Components hydrate on Preact for ~7.5 KB of framework, or ~12.5 KB with [`compat.react`](./compat.md) if you want to run React components and libraries unchanged. Offscreen islands can stay out of the initial bundle entirely with `dynamic({ load: 'visible' })`.
88
+
89
+ ### Streaming
90
+
91
+ Wrap slow server work in `<Suspense>`. The shell streams immediately and the content follows when it's ready:
92
+
93
+ ```tsx
94
+ import { Suspense } from '@wular/pnext'
95
+
96
+ export default function Page() {
97
+ return (
98
+ <Suspense fallback={<p>Loading comments…</p>}>
99
+ <Comments />
100
+ </Suspense>
101
+ )
102
+ }
103
+ ```
104
+
105
+ ### APIs and everything else
106
+
107
+ A `route.ts` file is an HTTP handler:
108
+
109
+ ```ts
110
+ // app/api/users/route.ts
111
+ export async function GET(request: NextRequest) {
112
+ return Response.json(await listUsers())
113
+ }
114
+ ```
115
+
116
+ - `proxy.ts` runs before route matching.
117
+ - `loading.tsx`, `error.tsx`, and `not-found.tsx` define per-segment fallbacks.
118
+ - `pnext build` makes the production build and `pnext start` serves it. Routes that never read the request are prerendered to static HTML.
119
+
120
+ ## References
121
+
122
+ - [Routing](./routing.md)
123
+ - [Navigation](./navigation.md)
124
+ - [Rendering](./rendering.md)
125
+ - [Metadata](./metadata.md)
126
+ - [CSS](./css.md)
127
+ - [Environment Variables](./env.md)
128
+ - [Config](./config.md)
129
+ - [Type Safety](./typegen.md)
130
+ - [Compatibility](./compat.md)
131
+ - [Development](./dev.md)
132
+ - [Performance](./performance.md)
@@ -1,12 +1,10 @@
1
1
  # Metadata
2
2
 
3
- Core metadata takes no request input, so it resolves at build time. `pnext build` evaluates metadata objects, `metadata()`/`viewport()` functions, and metadata route code, and `pnext start` serves the prebuilt head for static routes without recomputing it per request.
3
+ Titles, descriptions, icons, and social images for a route. Metadata takes no request input in core, so it all resolves during the build, and the server sends the prebuilt head for a static route without recomputing it.
4
4
 
5
- Use `compat.next` for Next.js request-time metadata APIs such as `generateMetadata()`, `generateViewport()`, streaming metadata, `headers()`, `cookies()`, and full `next/og` behavior.
5
+ ## metadata
6
6
 
7
- ## Page and Layout Exports
8
-
9
- Pages and layouts can export `metadata` as an object:
7
+ Pages and layouts export it as an object. Metadata resolves from the root layout down to the page, and nearer segments override or extend what earlier ones set.
10
8
 
11
9
  ```tsx
12
10
  import type { Metadata } from '@wular/pnext'
@@ -17,25 +15,24 @@ export const metadata: Metadata = {
17
15
  }
18
16
  ```
19
17
 
20
- They can also export `metadata()` as a no-arg build-time function:
18
+ ## Fields
19
+
20
+ pnext follows the Next.js field shapes for `title`, `description`, `metadataBase`, `openGraph`, `twitter`, `robots`, `icons`, `manifest`, `alternates`, `verification`, `appleWebApp`, `appLinks`, and `other`. The [Next.js metadata fields reference](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadata-fields) documents the full schema.
21
+
22
+ ## metadata()
23
+
24
+ A no-argument function export, for values that come from somewhere else. It runs during the build.
21
25
 
22
26
  ```tsx
23
27
  export async function metadata() {
24
28
  const site = await getSiteMetadata()
25
- return {
26
- title: site.title,
27
- description: site.description,
28
- }
29
+ return { title: site.title, description: site.description }
29
30
  }
30
31
  ```
31
32
 
32
- Metadata is resolved from root layout to page. Nearer segments override or extend earlier segments.
33
+ ## viewport
33
34
 
34
- pnext follows the Next.js metadata field shapes for common fields such as `title`, `description`, `metadataBase`, `openGraph`, `twitter`, `robots`, `icons`, `manifest`, `alternates`, `verification`, `appleWebApp`, `appLinks`, and `other`. Use the [Next.js metadata fields reference](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadata-fields) for the full field schema.
35
-
36
- ## Viewport
37
-
38
- Pages and layouts can export `viewport` as an object or a build-time function:
35
+ Pages and layouts export it as an object or as a build-time function, the same way.
39
36
 
40
37
  ```tsx
41
38
  import type { Viewport } from '@wular/pnext'
@@ -47,40 +44,28 @@ export const viewport: Viewport = {
47
44
  }
48
45
  ```
49
46
 
50
- Use `compat.next` for `generateViewport()` with route props.
51
-
52
- ## File Conventions
47
+ ## Metadata files
53
48
 
54
- Core supports static metadata files in route segments:
49
+ Drop these into a route segment and pnext picks them up:
55
50
 
56
- - `icon`, `apple-icon`, `opengraph-image`, `twitter-image` `.ico`, `.jpg`, `.jpeg`, `.png`, `.svg`, `.gif`, or `.webp`, optionally numbered (`icon2.png`)
57
- - `sitemap.xml`
58
- - `favicon.ico`, `robots.txt`, `manifest.json`, `manifest.webmanifest` app root only
51
+ - `icon`, `apple-icon`, `opengraph-image`, and `twitter-image`, as `.ico`, `.jpg`, `.jpeg`, `.png`, `.svg`, `.gif`, or `.webp`, optionally numbered as `icon2.png`.
52
+ - `sitemap.xml`.
53
+ - `favicon.ico`, `robots.txt`, `manifest.json`, and `manifest.webmanifest`, at the app root only.
59
54
 
60
- Static files are copied during `pnext build` and served from the built public output. Image and sitemap files apply to their segment and its descendants; the root-only files are global.
55
+ The build copies them into the public output. Image and sitemap files apply to their segment and its descendants, while the root-only files are global.
61
56
 
62
- ## Code Metadata Files
57
+ ## Generated metadata files
63
58
 
64
- Core also supports code variants of each, in `.tsx`, `.ts`, `.jsx`, `.js`, or `.mjs`: `icon`, `apple-icon`, `opengraph-image`, `twitter-image`, `sitemap`, plus root-only `robots` and `manifest`.
59
+ Each of the above has a code variant, written in `.tsx`, `.ts`, `.jsx`, `.js`, or `.mjs`: `icon`, `apple-icon`, `opengraph-image`, `twitter-image`, and `sitemap`, plus root-only `robots` and `manifest`.
65
60
 
66
- In core, these modules run at build time. Their generated links and route outputs are stored in the build output, and `pnext start` does not recompute them during page requests.
67
-
68
- Code metadata route handlers can keep their metadata-route signatures, including `generateImageMetadata()` and `generateSitemaps()`, but core treats their results as build artifacts. Request-time variants that depend on `headers()`, `cookies()`, or dynamic request state belong in `compat.next`.
61
+ These modules run at build time, and their generated links and route outputs are stored in the build output rather than recomputed per request. They can keep their metadata-route signatures, including `generateImageMetadata()` and `generateSitemaps()`, but core treats the results as build artifacts. Use `params()` to choose build-time params, as shown in [Type Safety](./typegen.md).
69
62
 
70
63
  ## Precedence
71
64
 
72
- File-based metadata has higher priority than page/layout metadata for images and icons. Explicit page/layout image metadata still wins when it defines its own image list.
73
-
74
- The request path determines which segment metadata files apply. Route groups and parallel-route slot directories do not add URL path segments, but their metadata file output names are disambiguated at build time.
75
-
76
- ## Static Params
65
+ File-based metadata outranks page and layout metadata for images and icons, unless the page or layout defines its own image list.
77
66
 
78
- Core dynamic routes use `params()` for build-time param selection:
67
+ The request path decides which segment's metadata files apply. Route groups and slot directories add no URL segment, and their metadata output names are disambiguated during the build.
79
68
 
80
- ```tsx
81
- export function params() {
82
- return [{ slug: 'intro' }]
83
- }
84
- ```
69
+ ## Request-time metadata
85
70
 
86
- `generateStaticParams()` is a Next compat API. In `compat.next`, pnext treats it as the fallback static params provider when a module does not export core `params()`.
71
+ Anything that depends on the request needs `compat.next`: `generateMetadata()`, `generateViewport()`, streaming metadata, `headers()`, `cookies()`, and full `next/og` behavior. Under compat, `generateStaticParams()` also serves as the fallback static params provider for a module with no `params()` export. See [Compatibility](./compat.md).
@@ -1,25 +1,10 @@
1
1
  # Navigation
2
2
 
3
- pnext navigation APIs are split by runtime so server routes do not import browser router code.
4
-
5
- ## Typed Hrefs
6
-
7
- Use `href()` when building URLs outside JSX.
8
-
9
- ```tsx
10
- import { href } from '@wular/pnext/navigation'
11
-
12
- const userHref = href('/users/[id]', {
13
- params: { id: 'ada' },
14
- search: { tab: 'runs' },
15
- })
16
- ```
17
-
18
- Dynamic params are checked from generated route types. Routes without params do not take a `params` object.
3
+ Links, prefetching, redirects, and the client router. Server and browser APIs sit in separate entry points, so a server route never pulls in router code meant for the browser.
19
4
 
20
5
  ## Link
21
6
 
22
- `<Link>` renders a normal anchor with typed route params and pnext prefetch metadata.
7
+ Renders a normal anchor. The href is a route template, and params are checked against the generated route types, so a wrong name or a missing param fails TypeScript. Use a plain anchor for external URLs and for links you do not want typed.
23
8
 
24
9
  ```tsx
25
10
  import { Link } from '@wular/pnext/link'
@@ -37,120 +22,88 @@ export function Nav() {
37
22
  }
38
23
  ```
39
24
 
40
- Supported props:
41
-
42
- - `href`: generated route path.
43
- - `params`: required for dynamic routes.
44
- - `search`: query params.
45
- - `hash`: hash fragment.
46
- - `prefetch`: `false`, `'intent'`, `'visible'`, or `'load'`.
47
- - `replace`: marks the navigation as history replace.
48
- - `scroll={false}`: preserves scroll on client navigation.
49
- - `onNavigate`: client-only same-origin navigation callback that can call `preventDefault()`.
50
- - `onPrefetchStart` / `onPrefetchFinish`: fire when this link's prefetch begins and settles, in every prefetch mode.
51
-
52
- Use a normal `<a>` for external URLs or intentionally untyped links.
53
-
54
- ## Soft Navigation
55
-
56
- `<Link>` clicks and `router.push()`/`router.replace()` swap the page in place instead of reloading the document, so shared chunks and CSS are never re-downloaded. Back and forward stay soft and restore scroll. Cross-origin targets, non-HTML responses, and fetch failures fall back to a full page load.
57
-
58
- `prefetch` on `<Link>` warms the target page and its assets:
59
-
60
- - `'intent'`: on hover, touch, or focus.
61
- - `'visible'` (default): when the link enters the viewport.
62
- - `'load'`: as soon as the link renders.
63
- - `false`: never; pair with `router.prefetch(href)` for manual control.
64
-
65
- Prefetch requests ride low network priority, so they never compete with page-critical loading, and concurrency is capped at 4 in flight (12 for hover intent). Prefetched pages expire after 5 minutes and each navigation refetches on the next visit, so pages always reflect fresh server state. Prefetch is a no-op under `pnext dev`.
25
+ | Prop | Value |
26
+ | ------------------------------------- | ------------------------------------------------------------- |
27
+ | `href` | Generated route path. |
28
+ | `params` | Required for dynamic routes. |
29
+ | `search` | Query params. |
30
+ | `hash` | Hash fragment. |
31
+ | `prefetch` | `false`, `'intent'`, `'visible'`, or `'load'`. |
32
+ | `replace` | Marks the navigation as a history replace. |
33
+ | `scroll` | `false` preserves scroll on client navigation. |
34
+ | `onNavigate` | Same-origin client callback that can call `preventDefault()`. |
35
+ | `onPrefetchStart`, `onPrefetchFinish` | Fire when this link's prefetch begins and settles. |
66
36
 
67
- ## Redirects
37
+ ## Prefetching
68
38
 
69
- Use `notFound()` to render the nearest `not-found.tsx` fallback with a 404 response.
39
+ Prefetch warms the target page and its assets. Set the mode per link with the `prefetch` prop, as in `<Link prefetch="intent">`.
70
40
 
71
- Use `redirect()` to stop server rendering and return a redirect response. In the browser it navigates directly.
41
+ - `'visible'` is the default, and fires when the link enters the viewport.
42
+ - `'intent'` fires on hover, touch, or focus.
43
+ - `'load'` fires as soon as the link renders.
44
+ - `false` never prefetches. Pair it with `router.prefetch(href)` for manual control.
72
45
 
73
- ```tsx
74
- import { redirect } from '@wular/pnext/navigation'
46
+ The app-wide default can be set with the `prefetch` field in `pnext.config.ts`, described in [Config](./config.md).
75
47
 
76
- export default async function Page() {
77
- const user = await getUser()
78
- if (!user) redirect('/login')
79
- return <Dashboard user={user} />
80
- }
81
- ```
48
+ Requests use low network priority. At most four run at once, though the hover-intent lane allows up to twelve. The core fallback expiry is five minutes. Prefetch does nothing in development.
82
49
 
83
- `redirect()` returns a temporary 307 redirect on the server. `permanentRedirect()` returns 308.
50
+ ## Soft navigation
84
51
 
85
- Client event handlers can call `redirect()` too. Keep it as the last statement, or `return redirect(...)`, because browser navigation does not synchronously stop the rest of the handler. Use `useRouter().push()` or `useRouter().replace()` when you want explicit history control.
52
+ Link clicks and router pushes swap the page in place instead of reloading the document, so shared chunks and CSS are never re-downloaded. Back and forward stay soft and restore scroll. Cross-origin targets, non-HTML responses, and fetch failures fall back to a full page load.
86
53
 
87
- For typed dynamic redirects, build the URL with `href()`:
54
+ ## Redirects and not found
88
55
 
89
56
  ```tsx
90
- import { href, redirect } from '@wular/pnext/navigation'
57
+ import { href, notFound, redirect } from '@wular/pnext/navigation'
91
58
 
59
+ redirect('/login')
60
+ redirect('/login', 308)
92
61
  redirect(href('/users/[id]', { params: { id: 'ada' } }))
62
+ notFound()
93
63
  ```
94
64
 
95
- ## Client Hooks
65
+ `redirect()` stops server rendering and returns a redirect response. Call it from pages, server components, route handlers, and any server helper used during rendering. Validate targets that come from user input.
96
66
 
97
- Client navigation hooks live in `@wular/pnext/navigation/client`.
67
+ The status is 307 unless you pass 303, 307, or 308. `permanentRedirect()` returns 308.
98
68
 
99
- ```tsx
100
- 'use client'
69
+ `notFound()` renders the nearest `not-found` fallback with a 404 response.
101
70
 
102
- import { useRouter, useSearchParams } from '@wular/pnext/navigation/client'
71
+ In the browser, `redirect()` navigates directly. Keep it as the last statement of an event handler, or return it, because browser navigation does not synchronously stop the rest of the handler. Use the router when you want explicit history control.
103
72
 
104
- export function SortButton() {
105
- const router = useRouter()
106
- const searchParams = useSearchParams()
73
+ ## href()
107
74
 
108
- return (
109
- <button
110
- onClick={() => {
111
- const search = new URLSearchParams(searchParams.toString())
112
- search.set('sort', 'asc')
113
- router.replace('/products', { search, scroll: false })
114
- }}
115
- >
116
- Sort
117
- </button>
118
- )
119
- }
120
- ```
75
+ Builds a typed URL outside JSX. Routes without params take no params object.
121
76
 
122
- Available hooks:
123
-
124
- - `useRouter()`: `push`, `replace`, `prefetch`, `refresh`, `back`, and `forward`.
125
- - `usePathname()`: current browser pathname.
126
- - `useSearchParams()`: read-only URL search params.
127
- - `useParams<Route>()`: current route params from the initial route state.
128
- - `useRoute<Route>()`: pathname, route template, params, and search params together.
129
- - `useLinkStatus()`: pending shape for link feedback.
77
+ ```ts
78
+ href('/users/[id]', {
79
+ params: { id: 'ada' },
80
+ search: { tab: 'runs' },
81
+ })
82
+ ```
130
83
 
131
- `push()` and `replace()` are typed like `href()`. They soft-navigate when the path or query changes and fall back to the History API for same-URL hash updates. Cross-origin targets become a full page load. `refresh()` re-fetches the current route in place. `useLinkStatus()` currently always reports `{ pending: false }`.
84
+ ## Client hooks
132
85
 
133
- ## Dynamic Components
86
+ Exported from `@wular/pnext/navigation/client`, for use in components marked `"use client"`.
134
87
 
135
- Use `dynamic()` for lazily loaded Client Components.
88
+ | Hook | Returns |
89
+ | -------------------- | ------------------------------------------------------------- |
90
+ | `useRouter()` | `push`, `replace`, `prefetch`, `refresh`, `back`, `forward`. |
91
+ | `usePathname()` | Current browser pathname. |
92
+ | `useSearchParams()` | Read-only URL search params. |
93
+ | `useParams<Route>()` | Current route params from the initial route state. |
94
+ | `useRoute<Route>()` | Pathname, route template, params, and search params together. |
95
+ | `useLinkStatus()` | Currently always `{ pending: false }`. |
136
96
 
137
- ```tsx
138
- import { dynamic } from '@wular/pnext/dynamic'
139
-
140
- const Chart = dynamic(
141
- () => import('./chart').then(module => module.Chart),
142
- {
143
- load: 'visible',
144
- },
145
- )
146
- ```
97
+ Router pushes and replaces are typed like links. They soft-navigate when the path or query changes, and fall back to the History API for same-URL hash updates. Cross-origin targets become a full page load. Refreshing re-fetches the current route in place.
147
98
 
148
- For default exports, use the shorter literal form:
99
+ `useSearchParams()` returns a `ReadonlyURLSearchParams`, whose mutation methods throw. Copy it before changing it.
149
100
 
150
- ```tsx
151
- const Chart = dynamic('./chart')
101
+ ```ts
102
+ const search = new URLSearchParams(searchParams.toString())
103
+ search.set('sort', 'asc')
104
+ router.replace('/products', { search, scroll: false })
152
105
  ```
153
106
 
154
- Literal dynamic imports work from Server Components and Client Components. If the target module starts with `"use client"`, pnext renders it as a Client Component island; otherwise it renders on the server.
107
+ ## Auth interrupts
155
108
 
156
- Dynamic Client Components loaded from Server Components render HTML on the server by default and hydrate when the chunk loads. Pass `ssr: false` for browser-only output. `load: 'visible'` implies browser-only output unless `ssr: true` is set. Inside Client Components, `dynamic()` is a browser lazy loader.
109
+ `forbidden()` and `unauthorized()` throw `PNextForbiddenError` and `PNextUnauthorizedError`, both exported from the server navigation module along with `ReadonlyURLSearchParams`. The functions are core exports, but rendering them as 403 and 401 responses needs the experimental `compat.next` auth interrupts. See [Routing](./routing.md#convention-files).