From 21b2eb8202202459d038ef66956678b0f8a92d7e Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Fri, 20 Mar 2026 13:22:51 +0100 Subject: [PATCH] Update documentaion. --- README.md | 24 +++++++++++++++++++++++- comparisons/NEXTJS.md | 21 +++++++++++++++++++++ dist/functions/server/watcher.js | 2 ++ package.json | 2 +- src/functions/server/watcher.tsx | 1 + 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0ff75d..b6c058b 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ The goal is a framework that is: - [CLI Commands](#cli-commands) - [Project Structure](#project-structure) - [File-System Routing](#file-system-routing) + - [Non-Routed Directories](#non-routed-directories) - [Pages](#pages) - [Basic Page](#basic-page) - [Server Function](#server-function) @@ -176,6 +177,27 @@ Bunext uses `Bun.FileSystemRouter` with Next.js-style routing. Pages live in `sr Dynamic route parameters (e.g. `[slug]`) are available in the `server` function via `ctx.req.url` or from the `query` field in the server response. +### Non-Routed Directories + +Directories whose name contains `--` or a parenthesis (`(` or `)`) are completely ignored by the router. Use this to co-locate helper components, utilities, or shared logic directly inside `src/pages/` alongside the routes that use them, without them becoming routes. + +| Naming pattern | Effect | +| --- | --- | +| `(components)/` | Ignored — not routed | +| `--utils--/` | Ignored — not routed | +| `--lib/` | Ignored — not routed | + +``` +src/pages/ +├── blog/ +│ ├── (components)/ # Not a route — co-location directory +│ │ ├── PostCard.tsx # Used by index.tsx and [slug].tsx +│ │ └── PostList.tsx +│ ├── index.tsx # Route: /blog +│ └── [slug].tsx # Route: /blog/:slug +└── index.tsx # Route: / +``` + --- ## Pages @@ -592,7 +614,7 @@ The cron job checks all cache entries every 30 seconds and deletes any whose age - **Cold start required.** The cache is populated on the first request; there is no pre-warming step. - **Immutable within the expiry window.** Once a page is cached, `writeCache` skips all subsequent write attempts for that key until the cron job deletes the expired entry. There is no manual invalidation API. - **Cache is not cleared on rebuild.** Deploying a new build does not automatically flush `public/__bunext/cache/`. Stale HTML files referencing old JS bundles can be served until they expire. Clear the cache directory as part of your deploy process if needed. -- **Key collision with dashes.** Cache keys are derived by replacing every `/` in the URL path with `-`. This means `/foo/bar` and `/foo-bar` produce the same cache filename and will share a cache entry. Avoid enabling `cachePage` on routes where a nested path and a dash-separated path could collide. +- **No key collision.** Cache keys are generated via `encodeURIComponent()` on the URL path. `/foo/bar` encodes to `%2Ffoo%2Fbar` and `/foo-bar` to `%2Ffoo-bar` — distinct filenames with no collision risk. --- diff --git a/comparisons/NEXTJS.md b/comparisons/NEXTJS.md index a952f9e..086447e 100644 --- a/comparisons/NEXTJS.md +++ b/comparisons/NEXTJS.md @@ -76,6 +76,7 @@ This report compares the two on their overlapping surface — server-side render | Dynamic routes `[param]` | ✅ | ✅ | ✅ | | Catch-all routes `[...slug]` | ✅ | ✅ | ✅ | | Optional catch-all `[[...slug]]` | ✅ | ✅ | ✅ | +| Non-routed co-location directories | ✅ | ❌ | ✅ (file-naming model) | | Route groups `(group)` | ❌ | ❌ | ✅ | | Parallel routes `@slot` | ❌ | ❌ | ✅ | | Intercepting routes `(..)` | ❌ | ❌ | ✅ | @@ -150,6 +151,20 @@ This report compares the two on their overlapping surface — server-side render Catch-all routes (`[...slug].tsx`) and optional catch-all routes (`[[...slug]].tsx`) are supported natively by `Bun.FileSystemRouter` and work in Bunext — the test project confirms this with `src/pages/blog/[[...all]]/index.tsx`. +Any directory inside `src/pages/` whose name contains `--` or a parenthesis (`(` or `)`) is **completely ignored by the router**. This lets developers co-locate helper components, hooks, and utilities directly alongside the routes that use them without any routing side effects: + +``` +src/pages/ +├── blog/ +│ ├── (components)/ ← Not a route — co-location directory +│ │ ├── PostCard.tsx +│ │ └── PostList.tsx +│ ├── index.tsx ← Route: /blog +│ └── [slug].tsx ← Route: /blog/:slug +``` + +Next.js Pages Router has no equivalent — every `.tsx` file in `pages/` becomes a route, so helper components must live outside the `pages/` tree entirely. Next.js App Router handles this differently via file-naming convention: only files named `page.tsx` or `route.ts` are routes, so any other file can already be co-located freely. Bunext's explicit directory-level marker (`(dir)` or `--dir--`) provides the same benefit on top of a Pages Router-style filesystem convention. + **Gaps:** - No route groups — every folder directly contributes to the URL structure. - Parallel and intercepting routes (App Router features) are absent. These enable patterns like modals that preserve the previous URL (intercepting) or simultaneously rendering multiple independent route segments. @@ -591,6 +606,12 @@ This model also composes naturally with runtime decisions: the `server` function Every page component automatically receives a `url` prop (a copy of the request `URL` object) even without a `server` function. In Next.js's Pages Router, `getServerSideProps` must be exported just to access the request URL. The App Router exposes `params` and `searchParams` but not a full `URL` object. +### Co-Location Directories + +Bunext's router ignores any directory inside `src/pages/` whose name contains `--` or a parenthesis. This lets helper components, hooks, and utilities live right next to the routes that use them — named `(components)`, `--utils--`, or similar — without any routing side effects. + +Next.js Pages Router has no equivalent. Every file in `pages/` becomes a route; helpers must live in a separate root-level directory (`components/`, `lib/`, etc.) rather than alongside the pages that use them. Next.js App Router handles co-location via file-naming convention (only `page.tsx`/`route.ts` are routes), so the problem doesn't arise — but it provides no explicit directory-level exclusion marker. + --- ## Where Bunext Lags diff --git a/dist/functions/server/watcher.js b/dist/functions/server/watcher.js index a0803b9..0e6b109 100644 --- a/dist/functions/server/watcher.js +++ b/dist/functions/server/watcher.js @@ -15,6 +15,8 @@ export default function watcher() { return; if (!filename.match(/^pages\//)) return; + if (filename.match(/\/(--|\()/)) + return; if (global.RECOMPILING) return; const fullPath = path.join(SRC_DIR, filename); diff --git a/package.json b/package.json index 9fb43c8..64ee7a2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@moduletrace/bunext", "module": "index.ts", "type": "module", - "version": "1.0.7", + "version": "1.0.5", "bin": { "bunext": "dist/index.js" }, diff --git a/src/functions/server/watcher.tsx b/src/functions/server/watcher.tsx index 85b7259..360f8a5 100644 --- a/src/functions/server/watcher.tsx +++ b/src/functions/server/watcher.tsx @@ -18,6 +18,7 @@ export default function watcher() { if (event !== "rename") return; if (!filename.match(/^pages\//)) return; + if (filename.match(/\/(--|\()/)) return; if (global.RECOMPILING) return;