@wular/pnext 0.0.6 → 0.0.7
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 +12 -6
- package/bin/pnext +5 -0
- package/package.json +1 -1
- package/reference/compat.md +86 -21
- package/reference/config.md +40 -93
- package/reference/css.md +40 -35
- package/reference/dev.md +25 -32
- package/reference/env.md +18 -20
- package/reference/getting-started.md +132 -0
- package/reference/metadata.md +26 -41
- package/reference/navigation.md +58 -105
- package/reference/performance.md +21 -142
- package/reference/rendering.md +73 -70
- package/reference/routing.md +87 -97
- package/reference/typegen.md +21 -27
- package/src/api/client-navigation.ts +11 -8
- package/src/api/link.tsx +5 -3
- package/src/cli/create.ts +1 -1
- package/src/cli/index.ts +6 -0
- package/src/client/build.ts +10 -2
- package/src/client/router/events.ts +15 -0
- package/src/client/router/runtime.ts +40 -9
- package/src/client/router/types.ts +5 -0
- package/src/config.ts +18 -1
- package/src/dev/server.ts +2 -2
- package/src/render/renderer.ts +11 -0
- package/src/routing/href.ts +19 -1
- package/src/runtime/loader.ts +3 -1
- package/src/runtime/vendor.ts +5 -4
- package/src/types.ts +5 -1
- package/reference/overview.md +0 -35
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
## Getting started
|
|
10
10
|
|
|
11
|
+
> pnext requires [Bun](https://bun.sh/get) - make sure it is installed first.
|
|
12
|
+
|
|
11
13
|
A new app:
|
|
12
14
|
|
|
13
15
|
```sh
|
|
@@ -22,11 +24,13 @@ bunx @wular/pnext migrate
|
|
|
22
24
|
|
|
23
25
|
Or by hand: `bun add -d @wular/pnext`, then `pnext dev`.
|
|
24
26
|
|
|
27
|
+
[Getting Started](./reference/getting-started.md) walks through all of it, from first page to build.
|
|
28
|
+
|
|
25
29
|
## Incremental by design
|
|
26
30
|
|
|
27
|
-
Server-rendered pages ship **0 KB** of JavaScript, or **~1 KB gzip** if you want client-side navigation and prefetching. Interactive pages hydrate on Preact for **~7.5 KB** of framework, **~12.5 KB** with React compatibility.
|
|
31
|
+
Server-rendered pages ship **0 KB** of JavaScript, or **~1 KB gzip** if you want client-side navigation and prefetching. Interactive pages hydrate on Preact for **~7.5 KB** of framework, **~12.5 KB** with React compatibility. Everything is instant, the first page in dev renders **10–12× faster** than Next.js on **3.5–4× less memory**, and production builds run **7–9× faster**. See [Performance](./reference/performance.md).
|
|
28
32
|
|
|
29
|
-
Core pnext is pure Preact. `compat.react` runs React components and libraries on it, and `compat.next` runs a whole Next.js App Router app unchanged. Start anywhere on that ladder and move when it suits you. The App Router compatibility is validated against Next's own test suite (4,400+ assertions passing). The `pages/` folder
|
|
33
|
+
Core pnext is pure Preact. `compat.react` runs React components and libraries on it, and `compat.next` runs a whole Next.js App Router app unchanged. Start anywhere on that ladder and move when it suits you. The App Router compatibility is validated against Next's own test suite (4,400+ assertions passing). The `pages/` folder or private internal utilities of Next.js or React are mostly not supported. See [Compatibility](./reference/compat.md).
|
|
30
34
|
|
|
31
35
|
## A quick tour
|
|
32
36
|
|
|
@@ -63,7 +67,9 @@ import { useState } from 'preact/hooks'
|
|
|
63
67
|
|
|
64
68
|
export function Counter({ initial }: { initial: number }) {
|
|
65
69
|
const [count, setCount] = useState(initial)
|
|
66
|
-
return
|
|
70
|
+
return (
|
|
71
|
+
<button onClick={() => setCount(count + 1)}>Count {count}</button>
|
|
72
|
+
)
|
|
67
73
|
}
|
|
68
74
|
```
|
|
69
75
|
|
|
@@ -98,14 +104,14 @@ export async function GET(request: NextRequest) {
|
|
|
98
104
|
|
|
99
105
|
- `proxy.ts` runs before route matching.
|
|
100
106
|
- `loading.tsx`, `error.tsx`, and `not-found.tsx` define per-segment fallbacks.
|
|
101
|
-
- `pnext build`
|
|
107
|
+
- `pnext build` makes the production build and `pnext start` serves it. Routes that never read the request are prerendered to static HTML.
|
|
102
108
|
|
|
103
109
|
## Learn more
|
|
104
110
|
|
|
105
111
|
Apps are file-routed from `app/`: `page.tsx` and `layout.tsx` are Server Components, `route.ts` files are HTTP handlers, `public/` is served from `/`. The reference covers the rest:
|
|
106
112
|
|
|
107
|
-
- [
|
|
108
|
-
- [
|
|
113
|
+
- [Getting Started](./reference/getting-started.md)
|
|
114
|
+
- [Development](./reference/dev.md)
|
|
109
115
|
- [Routing](./reference/routing.md)
|
|
110
116
|
- [Navigation](./reference/navigation.md)
|
|
111
117
|
- [Rendering](./reference/rendering.md)
|
package/bin/pnext
CHANGED
package/package.json
CHANGED
package/reference/compat.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Compatibility
|
|
2
2
|
|
|
3
|
-
pnext is Next.js-shaped. With `compat.next` on, App Router apps behave the way they do in Next: the `next/*` module surface, `next.config.js`, server actions, metadata,
|
|
3
|
+
pnext is Next.js-shaped. With `compat.next` on, App Router apps behave the way they do in Next: the `next/*` module surface, `next.config.js`, server actions, metadata, proxy and middleware, `'use cache'`, the `/_next/image` optimizer, and the `next/navigation` router. That is validated by running Next.js's own test suite against pnext, with 4,400+ assertions passing, rather than by hand-written parity claims.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
export default {
|
|
@@ -12,52 +12,117 @@ export default {
|
|
|
12
12
|
}
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
`compat.react` aliases `react`, `react-dom`, `react-dom/client`, `react-dom/server`, `react/jsx-runtime`, `react/jsx-dev-runtime`, and `react/compiler-runtime` onto Preact-backed shims.
|
|
15
|
+
`compat.react` aliases `react`, `react-dom`, `react-dom/client`, `react-dom/server`, `react/jsx-runtime`, `react/jsx-dev-runtime`, and `react/compiler-runtime` onto Preact-backed shims. React's `cache()` maps to pnext's request cache on the server. Importing `next/*` or `react` without the matching mode is a build error that names the flag to enable.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
To turn Next compatibility on without a config file, set `PNEXT_COMPAT=next`. See [Environment Variables](./env.md).
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
## Migrating a Next.js app
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
```sh
|
|
22
|
+
bunx @wular/pnext migrate --dry-run
|
|
23
|
+
bunx @wular/pnext migrate
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Migration rewrites `package.json` and `tsconfig.json`, and creates a `pnext.config.ts` with `compat.next` enabled. It reports on your app source but never edits it.
|
|
27
|
+
|
|
28
|
+
## What differs from Next
|
|
29
|
+
|
|
30
|
+
These are the places pnext deliberately does not match Next. Read them before migrating. Together with the caveats in the feature notes below, this is the whole list.
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
### Preact, not React
|
|
33
|
+
|
|
34
|
+
The streaming renderers from `react-dom/server`, `renderToReadableStream` and `renderToPipeableStream`, throw. The synchronous `renderToString` and `renderToStaticMarkup` work. Client components run on Preact's reconciler, so code reaching into React internals is out of scope.
|
|
24
35
|
|
|
25
36
|
Direct Preact imports and Preact Signals need no compat and produce smaller bundles than `preact/compat`.
|
|
26
37
|
|
|
27
|
-
|
|
38
|
+
### No Flight payload
|
|
39
|
+
|
|
40
|
+
Soft navigation and server-action responses carry HTML, not a React Flight stream. Streaming, refresh, and revalidation-driven updates all work. Consuming the RSC payload as a wire format does not.
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
Server-rendered JSX can cross into a Client Component as `children` or through any other prop, including nested in arrays and plain objects. Both stay server-rendered static HTML and ship no code, as in Next. Elements inside `Map` and `Set` props are the one shape that throws.
|
|
30
43
|
|
|
31
|
-
|
|
44
|
+
### Pages Router is emulated
|
|
32
45
|
|
|
33
|
-
|
|
46
|
+
A `pages/` directory is materialized onto App Router routes, so `getStaticProps` and `getServerSideProps` pages and `pages/api` handlers run. `_app`, `_document`, and `_error` are ignored. `next/head` renders nothing, so use the metadata exports instead. `next/router` maps onto the app router where the concepts line up and no-ops elsewhere.
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
### No webpack or Turbopack
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
esbuild is the only bundler. A `webpack(config)` function in `next.config.js` is not executed, and pnext warns once at config load when one is present. Loader chains from `turbopack.rules`, plus `turbopack.resolveAlias`, `transpilePackages`, `modularizeImports`, and `optimizePackageImports`, are re-implemented on esbuild directly.
|
|
38
51
|
|
|
39
|
-
|
|
52
|
+
A `webpack()` function that references `@svgr/webpack`, by far the most common custom-loader use, is auto-detected. pnext then compiles `.svg` imports to inline Preact components, matching that loader's default output. Root SVG attributes are spread first, so props such as `className`, `width`, and `height` override them. Without that reference, `.svg` imports keep the normal static-asset URL behavior.
|
|
40
53
|
|
|
41
|
-
|
|
54
|
+
### Cache state is per process
|
|
42
55
|
|
|
43
|
-
|
|
56
|
+
`revalidatePath`, `revalidateTag`, `unstable_cache`, and `'use cache'` entries live in the server process by default, so a multi-instance deployment revalidates one instance. Configure `cacheHandler` in `next.config.js` for a shared store.
|
|
44
57
|
|
|
45
|
-
|
|
58
|
+
### Optional native dependencies
|
|
59
|
+
|
|
60
|
+
All three ship as `optionalDependencies`, so a normal install has them, the same as Next. These paths only matter when the optional install fails.
|
|
46
61
|
|
|
47
62
|
- `next/image`'s `/_next/image` optimizer needs `sharp`. The component, `images` config validation, and static imports work without it.
|
|
48
63
|
- `next/og`'s `ImageResponse` needs `satori` and `@resvg/resvg-js`. Without them, or without a usable font, it answers with a valid placeholder PNG instead of failing the request.
|
|
49
64
|
- `next/font/google` resolves the catalog through `next-font`. If it cannot, the build fails rather than falling back to a hosted font.
|
|
50
65
|
|
|
51
|
-
|
|
66
|
+
### Smaller surfaces
|
|
52
67
|
|
|
53
|
-
- `userAgent()` uses an in-house parser covering mainstream browsers, engines, CPUs, and devices
|
|
54
|
-
-
|
|
68
|
+
- `userAgent()` uses an in-house parser covering mainstream browsers, engines, CPUs, and devices, not the full ua-parser-js database.
|
|
69
|
+
- React's taint functions do not exist under Preact, so pnext does not export them. What it implements is the guarantee `experimental.taint` exists for: with the flag on, `process.env` is registered as tainted, and passing that object as a client-component prop at any depth throws. Development shows the message in the nearest error boundary and production shows React's redacted error text, matching Next. Tainting your own objects or values is not available.
|
|
70
|
+
- `ViewTransition` and `addTransitionType` are exported from the `react` entry so pages importing them render instead of throwing, and `next/link` accepts `transitionTypes`. Support stops there: the component is a passthrough that renders no DOM, and pnext does not drive `document.startViewTransition`, so declared names and types are recorded but no browser transition is played.
|
|
71
|
+
- Only these `next/dist/*` paths are shimmed. Any other deep import fails with an error naming this list.
|
|
55
72
|
- `next/dist/client/components/app-router-headers`
|
|
56
73
|
- `next/dist/server/web/spec-extension/unstable-cache`
|
|
57
74
|
- `next/dist/server/web/spec-extension/unstable-no-store`
|
|
58
75
|
- `next/dist/server/web/spec-extension/revalidate`
|
|
59
76
|
- `next/dist/server/app-render/work-unit-async-storage.external` (server only)
|
|
60
77
|
|
|
61
|
-
##
|
|
78
|
+
## Feature notes
|
|
79
|
+
|
|
80
|
+
The rest of the surface ships as well. Each note says where support stops short.
|
|
81
|
+
|
|
82
|
+
### redirects and rewrites
|
|
83
|
+
|
|
84
|
+
`redirects()` and `rewrites()` in `next.config.js` are both honored. Sources support the `:param`, `:param*`, `:param+`, `:param?`, and `:param(regex)` tokens, plus `has` and `missing` conditions on host, header, query, and cookie. Named capture groups feed their values into destination parameters.
|
|
85
|
+
|
|
86
|
+
Rewrites accept the array form and the object form with `beforeFiles`, `afterFiles`, and `fallback`, and entries setting `basePath: false` match the raw path. An external `http` or `https` destination is proxied through a server-side fetch. After a rewrite fires, `usePathname()` and `useSearchParams()` still report the URL the browser asked for.
|
|
87
|
+
|
|
88
|
+
Redirects use 308 for `permanent: true`, 307 for `permanent: false`, or an explicit `statusCode`, and they keep external destinations as redirects.
|
|
89
|
+
|
|
90
|
+
One partial: fallback rewrites apply only to requests that would otherwise 404, and in development that check consults the route table alone, since there is no built output to look at.
|
|
91
|
+
|
|
92
|
+
### after()
|
|
93
|
+
|
|
94
|
+
`after()` from `next/server` runs work once the response is fully sent. Each callback runs exactly once, when the response closes, on every path: stream end, a redirect, a not-found, a thrown error, or a client abort. Calls nested inside an `after()` task run too. When the host platform supplies the Vercel request context, each task is also handed to its `waitUntil` so a serverless invocation stays alive until the task settles. An `after()` task that throws during a build prerender fails the build rather than quietly degrading the route.
|
|
95
|
+
|
|
96
|
+
### next/form
|
|
97
|
+
|
|
98
|
+
`Form` renders a GET form and intercepts submission into a client-side navigation, building the destination URL from the form's fields. String actions get the basePath applied and are prefetched like a link, including a re-prefetch when a revalidation invalidates them. Function actions pass straight through as React form actions with no interception. A submitter that overrides the encoding, method, or target falls back to the browser's native submit, and file inputs are not submitted with a string action. Both cases warn in development.
|
|
99
|
+
|
|
100
|
+
### instrumentation and instrumentation-client
|
|
101
|
+
|
|
102
|
+
An `instrumentation` file at the project root or in `src/` is bundled and imported once at server start. Its `register()` is awaited before the first request is served, and its `onRequestError` export is wired into the error funnel. When the app contains any edge entity, meaning a proxy or middleware, or a route declaring an edge runtime, a second freshly loaded instance is registered with `NEXT_RUNTIME` set to `edge`, mirroring Next's separate edge boot.
|
|
103
|
+
|
|
104
|
+
An `instrumentation-client` file is bundled with any `instrumentationClientInject` entries ahead of it, in configured order, and loaded from the document head so it runs before hydration. Each module's `onRouterTransitionStart` export is called at the start of every soft navigation. Apps without such a file get no extra bundle and no extra head tag.
|
|
105
|
+
|
|
106
|
+
### OpenTelemetry
|
|
107
|
+
|
|
108
|
+
pnext emits Next's span taxonomy through the global `@opentelemetry/api` that your instrumentation file registers: a root request span carrying `next.route` and `http.status_code`, plus child spans for rendering, route handlers, `fetch`, middleware and proxy, and Pages Router data and API handlers. Incoming `traceparent` headers are extracted, and errors caught by the request funnel mark the root span.
|
|
109
|
+
|
|
110
|
+
`@opentelemetry/api` is an optional dependency resolved from your own `node_modules`, so pnext and your SDK share one API singleton. When the package is absent, every tracing helper is inert. Keys listed in `experimental.clientTraceMetadata` are injected into the document head as meta tags.
|
|
111
|
+
|
|
112
|
+
### MDX
|
|
113
|
+
|
|
114
|
+
`.mdx` and `.md` modules compile through `@mdx-js/mdx`, an optional dependency loaded on the first MDX compile, so an app that never imports MDX does not need it installed. `createMDX()` from `@next/mdx` is understood at config load, so the remark, rehype, and recma plugins you configure there run. MDX files become routes only when `pageExtensions` lists the extension, matching Next. An `mdx-components` file supplies the component provider, and without one MDX emits plain host elements. One caveat: MDX currently compiles in the client graph, so treat an MDX module as client code.
|
|
115
|
+
|
|
116
|
+
### Edge runtime
|
|
117
|
+
|
|
118
|
+
A route or proxy declaring an edge runtime, and a Pages Router handler configured for `edge` or `experimental-edge`, runs with `process.env.NEXT_RUNTIME` set to `edge`, the `EdgeRuntime` global defined, and a `process` object that hides `version` and `versions` so code branching on those detects the edge environment. This is an emulation inside the same Bun process rather than a separate isolate, so the Edge API subset is not enforced: Node built-ins stay reachable, and code that only works because of that will still fail on a real edge platform.
|
|
119
|
+
|
|
120
|
+
### Root params
|
|
121
|
+
|
|
122
|
+
`next/root-params` resolves parameters from the root dynamic segment. It works in layouts, pages, and `'use cache'` functions, and inside `generateStaticParams` when a parent `generateStaticParams` already provided the parameter. Calling it inside a server action, inside `unstable_cache`, or from a route handler throws with the same diagnostics Next produces. Reading one marks every segment of the response as varying.
|
|
123
|
+
|
|
124
|
+
### output: 'export' and output: 'standalone'
|
|
125
|
+
|
|
126
|
+
`output: 'export'` writes a static tree to `out/`, or to `distDir` when the app configures a custom one. The tree carries the HTML and the flat per-page artifacts the client router fetches when no pnext server is present, the client runtime under `_next/static/chunks/`, the build manifests, the `public/` tree, and the not-found page. Dynamic routes without `generateStaticParams`, pages forcing dynamic rendering, and route handlers with disallowed segment config are build errors, as they are in Next.
|
|
62
127
|
|
|
63
|
-
`
|
|
128
|
+
`output: 'standalone'` writes `.next/standalone/` with a `server.js` that boots on `PORT` and `HOSTNAME`, along with a `.nft.json` trace beside each page entry and a middleware manifest. Because pnext's production server runs under Bun, that launcher is a thin Node script that re-executes the real pnext server pointed back at the original build directory. The folder is therefore not a self-contained bundle you can ship on its own, because the build tree has to travel with it.
|
package/reference/config.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Config
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An optional `pnext.config.ts` in the project root, meaning the directory passed to the CLI.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import type {
|
|
6
|
+
import type { PNextConfig } from '@wular/pnext'
|
|
7
7
|
|
|
8
8
|
export default {
|
|
9
9
|
outDir: '.pnext',
|
|
@@ -12,109 +12,56 @@ export default {
|
|
|
12
12
|
compat: {
|
|
13
13
|
next: true,
|
|
14
14
|
},
|
|
15
|
-
} satisfies
|
|
15
|
+
} satisfies PNextConfig
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
With `compat.next`, `next.config.js
|
|
18
|
+
This is the form `pnext create` scaffolds. With `compat.next`, pnext also loads `next.config.js`, and that file wins for the options both can set: `basePath`, `assetPrefix`, `outDir`, `trailingSlash`, `skipTrailingSlashRedirect`, and `productionBrowserSourceMaps`.
|
|
19
19
|
|
|
20
20
|
## Fields
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
| Field | Default | What it does |
|
|
23
|
+
| ----------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| `outDir` | `'.pnext'` | Build output for HTML, client assets, cache files, and `manifest.json`. |
|
|
25
|
+
| `basePath` | `''` | Path prefix for an app served below the domain root. |
|
|
26
|
+
| `assetPrefix` | `basePath` | URL prefix for emitted assets. Set it when assets come from a CDN. |
|
|
27
|
+
| `trailingSlash` | `false` | Canonicalizes URLs to a trailing slash and redirects the form without one. |
|
|
28
|
+
| `skipTrailingSlashRedirect` | `false` | Serves both slash forms with no canonical redirect, preserving authored hrefs. |
|
|
29
|
+
| `productionBrowserSourceMaps` | `false` | Emits external `.js.map` files next to production client chunks. |
|
|
30
|
+
| `prefetch` | `'visible'` | Default prefetch mode for links that set none of their own. See [Navigation](./navigation.md#prefetching). |
|
|
31
|
+
| `workspaceRoot` | inferred | Monorepo root for resolving and reloading local workspace package imports. |
|
|
32
|
+
| `htmlLimitedBots` | see below | User agents that get blocking metadata in the head. Applies with `compat.next`. |
|
|
33
|
+
| `adapter` | unset | Narrows what a deployment adapter packs into its server function. |
|
|
34
|
+
| `compat` | all `false` | Turns on the React, Next, and React Compiler compatibility layers. |
|
|
23
35
|
|
|
24
|
-
|
|
36
|
+
Worth knowing:
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
- Development client output is already unminified, so it never emits sourcemaps.
|
|
39
|
+
- `workspaceRoot` is inferred from `package.json#workspaces` or `pnpm-workspace.yaml`, otherwise the app root. It does not change the app root used for `public/`, `.pnext/`, or `pnext.config.ts`. Set it only to override the inference.
|
|
40
|
+
- The `htmlLimitedBots` default is a regex covering Twitterbot, Slackbot, Bingbot, Discordbot, LinkedInBot, and the Google and Lighthouse renderers. Matching user agents receive metadata blocked in the head instead of streamed into the body.
|
|
27
41
|
|
|
28
|
-
|
|
42
|
+
## Compat modes
|
|
29
43
|
|
|
30
|
-
|
|
44
|
+
| Flag | Effect |
|
|
45
|
+
| ---------------------- | -------------------------------------------------------------------------- |
|
|
46
|
+
| `compat.react` | Aliases the React and React DOM entry points to Preact-backed shims. |
|
|
47
|
+
| `compat.next` | Adds the `next/*` surface, `next.config.js`, and Next App Router behavior. |
|
|
48
|
+
| `compat.reactCompiler` | Experimental React Compiler support for React-style Client Components. |
|
|
31
49
|
|
|
32
|
-
|
|
50
|
+
Both `compat.next` and `compat.reactCompiler` imply `compat.react`. Direct Preact imports stay smaller than any of them, so reach for compat when you are running React-style components. React Compiler is not intended for components built around Preact Signals. See [Compatibility](./compat.md).
|
|
33
51
|
|
|
34
|
-
|
|
52
|
+
## Deployment adapter
|
|
35
53
|
|
|
36
|
-
|
|
54
|
+
`exclude` and `keep` are string lists that adjust which directories and file suffixes an adapter packs. The Vercel adapter consumes them.
|
|
37
55
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
### `skipTrailingSlashRedirect`
|
|
47
|
-
|
|
48
|
-
Default: `false`
|
|
49
|
-
|
|
50
|
-
Serve both the slashed and unslashed URL without a canonical redirect, and leave
|
|
51
|
-
`<Link>` hrefs exactly as authored.
|
|
52
|
-
|
|
53
|
-
### `productionBrowserSourceMaps`
|
|
54
|
-
|
|
55
|
-
Default: `false`
|
|
56
|
-
|
|
57
|
-
Emit external `.js.map` browser sourcemaps next to each production client chunk,
|
|
58
|
-
linked by a `//# sourceMappingURL=` comment. Off by default (as in Next): maps
|
|
59
|
-
publish your first-party source to every visitor and cost real build time.
|
|
60
|
-
Development never emits them — dev client output is un-minified already.
|
|
61
|
-
|
|
62
|
-
### `htmlLimitedBots`
|
|
63
|
-
|
|
64
|
-
Default: a regex covering Twitterbot, Slackbot, Bingbot, Discordbot, LinkedInBot,
|
|
65
|
-
and the Google/Lighthouse renderers.
|
|
66
|
-
|
|
67
|
-
User agents matched by this pattern get metadata blocked in `<head>` rather than
|
|
68
|
-
streamed into the body. `compat.next` only.
|
|
69
|
-
|
|
70
|
-
### `adapter`
|
|
71
|
-
|
|
72
|
-
Default: unset
|
|
73
|
-
|
|
74
|
-
`exclude` and `keep` string lists overriding what a deployment adapter packs into
|
|
75
|
-
the server function. Entries are directory names (`storybook-static`) or file
|
|
76
|
-
suffixes (`.map`).
|
|
77
|
-
|
|
78
|
-
### `workspaceRoot`
|
|
79
|
-
|
|
80
|
-
Default: inferred from `package.json#workspaces` or `pnpm-workspace.yaml`, otherwise the app root.
|
|
81
|
-
|
|
82
|
-
Monorepo root used to resolve and reload local workspace package imports. This does not change where pnext reads `public/`, writes `.pnext/`, or loads `pnext.config.ts`.
|
|
83
|
-
|
|
84
|
-
### `compat.react`
|
|
85
|
-
|
|
86
|
-
Default: `false`
|
|
87
|
-
|
|
88
|
-
When `true`, pnext aliases `react`, `react-dom`, `react-dom/client`, `react-dom/server`, `react/jsx-runtime`, `react/jsx-dev-runtime`, and `react/compiler-runtime` onto Preact-backed shims.
|
|
89
|
-
|
|
90
|
-
Use this when moving React-style components into a pnext app. Direct Preact imports are still smaller; `preact/compat` adds compatibility code only when your app imports React APIs.
|
|
91
|
-
|
|
92
|
-
### `compat.next`
|
|
93
|
-
|
|
94
|
-
Default: `false`
|
|
95
|
-
|
|
96
|
-
Enables the `next/*` module surface, `next.config.js`, and the rest of the Next App Router behaviors for existing apps and shared packages. This implies `compat.react`.
|
|
97
|
-
|
|
98
|
-
See [Compatibility](./compat.md).
|
|
99
|
-
|
|
100
|
-
### `compat.reactCompiler`
|
|
101
|
-
|
|
102
|
-
Default: `false`
|
|
103
|
-
|
|
104
|
-
Experimental React Compiler support for React-style Client Components. This implies `compat.react`.
|
|
105
|
-
|
|
106
|
-
Use this for components written with React hooks and props-heavy render paths where compiler memoization can reduce rerenders. Direct Preact code still gives the smallest bundles, and React Compiler compat is not recommended for components built around Preact Signals.
|
|
107
|
-
|
|
108
|
-
## File Conventions
|
|
109
|
-
|
|
110
|
-
Routes are loaded from `app/` or `src/app/`. If both exist, `app/` wins. Under
|
|
111
|
-
`compat.next`, a `pages/` directory is materialized onto App Router routes; a
|
|
112
|
-
hybrid app keeps its native `app/` routes.
|
|
113
|
-
|
|
114
|
-
Static assets are loaded from `public/` at the project root.
|
|
115
|
-
|
|
116
|
-
## Workspace Root
|
|
56
|
+
```ts
|
|
57
|
+
export default {
|
|
58
|
+
adapter: {
|
|
59
|
+
exclude: ['storybook-static', '.map'],
|
|
60
|
+
keep: ['runtime-assets', '.wasm'],
|
|
61
|
+
},
|
|
62
|
+
} satisfies PNextConfig
|
|
63
|
+
```
|
|
117
64
|
|
|
118
|
-
|
|
65
|
+
## Where pnext looks for files
|
|
119
66
|
|
|
120
|
-
|
|
67
|
+
Routes come from `app/` or `src/app/`, and `app/` wins if both exist. Static assets come from `public/` at the project root. Under `compat.next`, a `pages/` directory is materialized onto App Router routes, and a hybrid app keeps its native `app/` routes.
|
package/reference/css.md
CHANGED
|
@@ -1,55 +1,31 @@
|
|
|
1
1
|
# CSS
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Import global styles from the root layout. pnext compiles global CSS once and links it from every page as `/assets/global.css`.
|
|
3
|
+
Global styles, per-route styles, CSS Modules, and the compat-only preprocessors.
|
|
6
4
|
|
|
7
|
-
Global CSS
|
|
8
|
-
|
|
9
|
-
## PostCSS
|
|
5
|
+
## Global CSS
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
Imported from the root layout. pnext compiles it once and links it from every page as `/assets/global.css`.
|
|
12
8
|
|
|
13
9
|
```tsx
|
|
14
|
-
|
|
10
|
+
// app/layout.tsx
|
|
15
11
|
import './globals.css'
|
|
16
|
-
|
|
17
|
-
export default function Layout({
|
|
18
|
-
children,
|
|
19
|
-
}: {
|
|
20
|
-
children: ComponentChildren
|
|
21
|
-
}) {
|
|
22
|
-
return (
|
|
23
|
-
<html>
|
|
24
|
-
<body>{children}</body>
|
|
25
|
-
</html>
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
12
|
```
|
|
29
13
|
|
|
30
|
-
|
|
14
|
+
Global CSS can import local workspace packages, font files, image assets, and the app's PostCSS or Tailwind setup.
|
|
15
|
+
|
|
16
|
+
## Route and component CSS
|
|
31
17
|
|
|
32
|
-
|
|
18
|
+
Any page, layout, or component can import a stylesheet.
|
|
33
19
|
|
|
34
20
|
```tsx
|
|
35
21
|
import './page.css'
|
|
36
|
-
|
|
37
|
-
export default function Page() {
|
|
38
|
-
return <h1 className="title">Hello</h1>
|
|
39
|
-
}
|
|
40
22
|
```
|
|
41
23
|
|
|
42
|
-
pnext emits
|
|
24
|
+
pnext emits CSS only for routes that import it. A route stylesheet is named from the route id, as `/assets/<route-id>.css`. When a compat build splits one into chunks, each name gains an index suffix.
|
|
43
25
|
|
|
44
26
|
## CSS Modules
|
|
45
27
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```css
|
|
49
|
-
.title {
|
|
50
|
-
color: rebeccapurple;
|
|
51
|
-
}
|
|
52
|
-
```
|
|
28
|
+
A `.module.css` file gives scoped class names that match the server-rendered HTML.
|
|
53
29
|
|
|
54
30
|
```tsx
|
|
55
31
|
import styles from './page.module.css'
|
|
@@ -59,4 +35,33 @@ export default function Page() {
|
|
|
59
35
|
}
|
|
60
36
|
```
|
|
61
37
|
|
|
62
|
-
|
|
38
|
+
Under `compat.next`, a `:global(...)` wrapper leaves the enclosed selector unscoped.
|
|
39
|
+
|
|
40
|
+
## Tailwind and PostCSS
|
|
41
|
+
|
|
42
|
+
A `postcss.config.{cjs,js,mjs}` file at the app root makes pnext run its plugins on every emitted stylesheet. Tailwind v4 stays warm in development. Editing the config file requires a dev-server restart.
|
|
43
|
+
|
|
44
|
+
## Sass
|
|
45
|
+
|
|
46
|
+
Requires `compat.next` and the optional `sass` dependency. `.scss`, `.sass`, `.module.scss`, and `.module.sass` imports compile, and `sassOptions.includePaths` from `next.config.js` is honored. If `sass` is unavailable, Sass imports produce no CSS rather than breaking a non-Sass app.
|
|
47
|
+
|
|
48
|
+
## styled-jsx
|
|
49
|
+
|
|
50
|
+
Requires `compat.next`. Style blocks and `styled-jsx/css` are transformed to pnext's own runtime, which collects server styles and mirrors client styles into the document head. Apps do not need to install `styled-jsx`.
|
|
51
|
+
|
|
52
|
+
## Lightning CSS
|
|
53
|
+
|
|
54
|
+
Requires `compat.next` and the app's own `lightningcss` package.
|
|
55
|
+
|
|
56
|
+
- `experimental.useLightningcss` transforms CSS with it.
|
|
57
|
+
- `experimental.lightningCssFeatures` controls which features are included or excluded.
|
|
58
|
+
|
|
59
|
+
CSS passes through untransformed when the package is absent or the transform fails.
|
|
60
|
+
|
|
61
|
+
## Nonces and inline CSS
|
|
62
|
+
|
|
63
|
+
pnext reads a CSP nonce from the request `Content-Security-Policy` header and applies it to generated styles. With `experimental.inlineCss`, production documents inline route and global CSS in nonce-bearing style tags, while development keeps stylesheet links.
|
|
64
|
+
|
|
65
|
+
## Chunking and order
|
|
66
|
+
|
|
67
|
+
`experimental.cssChunking` groups compatible route stylesheets into fewer files while preserving each route's stylesheet order. It splits them instead when sharing a chunk would change cascade order or make a chunk too large.
|
package/reference/dev.md
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Development
|
|
2
2
|
|
|
3
|
-
`pnext dev`
|
|
3
|
+
The `pnext` CLI carries the whole flow: `dev` while you build, `analyze` to inspect what ships, `build` and `start` for production.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```txt
|
|
5
|
+
```sh
|
|
8
6
|
pnext dev [directory] [--port 3000] [--hostname 127.0.0.1]
|
|
9
7
|
pnext build [directory] [--adapter vercel] [--verbose]
|
|
10
8
|
pnext start [directory] [--port 3000] [--hostname 127.0.0.1]
|
|
11
9
|
pnext analyze [route] [directory] [--brotli] [--files] [--json]
|
|
12
10
|
pnext typegen [directory]
|
|
11
|
+
pnext create <directory> [--no-install]
|
|
12
|
+
pnext migrate [directory] [--dry-run]
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
`
|
|
16
|
-
|
|
17
|
-
Add `@wular/pnext` as a package dependency and put `pnext` in package scripts:
|
|
15
|
+
`create` scaffolds a new app with everything set up. `migrate` converts a Next.js project in place: it rewrites `package.json` and `tsconfig.json` and creates a `pnext.config.ts` with `compat.next` enabled, without editing app source; `--dry-run` previews. To set a project up by hand, add the package and scripts:
|
|
18
16
|
|
|
19
17
|
```json
|
|
20
18
|
{
|
|
@@ -30,40 +28,35 @@ Add `@wular/pnext` as a package dependency and put `pnext` in package scripts:
|
|
|
30
28
|
}
|
|
31
29
|
```
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
## Dev server
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
`pnext dev` starts a Bun HTTP server that scans the route tree, renders matching pages on request, and serves static assets from `public/`. Client route entries are built on demand and cached in `.pnext/cache`. Browser pages reload on route-tree changes over a server-sent events stream at `/__pnext/events`; the other `/__pnext/*` endpoints are implementation details, not application routes.
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
up as a single `pnext-dev` server process, `pnext build` as `pnext-build`, and
|
|
39
|
-
esbuild's service process as `pnext-esbuild`. Tailwind and PostCSS run in a
|
|
40
|
-
worker thread inside the server process, not as child processes.
|
|
35
|
+
The dev server does not typecheck in the request path; run your package's lint and typecheck scripts separately. `pnext typegen` regenerates the route types on demand; see [Type Safety](./typegen.md).
|
|
41
36
|
|
|
42
|
-
The dev server re-execs itself
|
|
43
|
-
`PNEXT_DEV_MAX_RSS_MB` (default 2048). The esbuild service is stopped and
|
|
44
|
-
respawned past `PNEXT_DEV_MAX_ESBUILD_RSS_MB` (default 1024).
|
|
37
|
+
In Activity Monitor and `ps`, the dev server runs as `pnext-dev`, builds as `pnext-build`, and the bundler service as `pnext-esbuild`. The dev server re-execs itself when its memory passes `PNEXT_DEV_MAX_RSS_MB` (default 2048), and restarts the bundler past `PNEXT_DEV_MAX_ESBUILD_RSS_MB` (default 1024).
|
|
45
38
|
|
|
46
|
-
##
|
|
39
|
+
## Analyze
|
|
47
40
|
|
|
48
|
-
|
|
49
|
-
- Render matching pages on request.
|
|
50
|
-
- Serve static assets from `public/`.
|
|
51
|
-
- Build client route entries on demand with esbuild.
|
|
52
|
-
- Cache generated client entries in `.pnext/cache`.
|
|
53
|
-
- Notify the browser of route-tree changes through `/__pnext/events`.
|
|
41
|
+
`pnext analyze` reports the client JavaScript behind each route. Optionally pass a route, either a template like `/users/[id]` or a concrete path, to report on that route only. `--files` breaks the report into files, `--brotli` measures with brotli instead of gzip, and `--json` emits machine-readable output.
|
|
54
42
|
|
|
55
|
-
##
|
|
43
|
+
## Build and run
|
|
56
44
|
|
|
57
|
-
|
|
45
|
+
`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. A `compat.next` build typechecks off-thread alongside bundling.
|
|
58
46
|
|
|
59
|
-
|
|
47
|
+
Debug flags: `--experimental-build-mode compile|generate` splits the build into its two phases, `--debug-build-paths <paths>` narrows diagnostics to matching paths, and `--debug-prerender` prints prerender diagnostics.
|
|
60
48
|
|
|
61
|
-
|
|
49
|
+
## Deploy
|
|
62
50
|
|
|
63
|
-
|
|
51
|
+
pnext deploys anywhere Bun runs: a VPS, a container, or any host you control. Run `pnext build` on the machine or in CI, then `pnext start` serves the app on your port.
|
|
64
52
|
|
|
65
|
-
|
|
53
|
+
Vercel has a dedicated adapter: `pnext build --adapter vercel` writes Build Output to `.vercel/output`. Static pages and static route-handler responses are emitted as files; everything dynamic runs in a single `_pnext` function on Vercel's Bun runtime.
|
|
66
54
|
|
|
67
|
-
##
|
|
55
|
+
## Environment variables
|
|
68
56
|
|
|
69
|
-
|
|
57
|
+
- `PNEXT_COMPAT=next`: Next compatibility without a config file. See [Compatibility](./compat.md).
|
|
58
|
+
- `PNEXT_TYPECHECK=classic`: in-process TypeScript checker instead of the native one.
|
|
59
|
+
- `PNEXT_CLIENT_METAFILE=1`: write the client esbuild metafile to the output directory.
|
|
60
|
+
- `PNEXT_CLIENT_PROFILE=1`: print client-build phase timings.
|
|
61
|
+
- `PNEXT_DEV_PROFILE=1`: print dev request and build timings.
|
|
62
|
+
- `PNEXT_BOOT_TRACE=1`: print boot-phase timings and memory readings.
|