Folder-Based Routing
Learn how folders define URL segments and create nested routes automatically in Bini.js.
Overview
Bini.js uses a folder-based routing system where the folder structure inside src/app/ directly maps to URL paths. This makes routing intuitive and eliminates the need for manual route configuration.
Folders map directly to URL segments
Create dynamic routes with [param] syntax
Match multiple segments with [...] syntax
Exclude folders with _ prefix
Basic Folder Routing
Each folder inside src/app/ becomes a URL segment. Add a page.tsx file inside to make the route publicly accessible.
This creates four routes: /, /about, /blog, and /contact.
Flat File Support
Bini.js also supports flat files at the root level. These work the same way as folder-based routes:
Nested Routes
Nest folders inside each other to create nested URL segments. Each level adds another segment to the URL path.
The folder structure directly mirrors the URL structure. Deep nesting is fully supported up to 100 levels deep.
layout.tsx that wraps all routes in that folder.Dynamic Segments
Use square brackets [param] to create dynamic route segments that match any value. Access the value with useParams() (auto-imported).
Dynamic Folder Example
Using Dynamic Params
Flat File Dynamic Routes
/^[a-zA-Z_][a-zA-Z0-9_]*$/ and are validated at scan time.Catch-all Segments
Use [...segment] to match multiple URL segments. The parameter becomes an array of values.
Catch-all Example
| URL | slug value |
|---|---|
| /docs/getting-started | ['getting-started'] |
| /docs/api/reference | ['api', 'reference'] |
| /docs/guides/routing/basics | ['guides', 'routing', 'basics'] |
Flat File Catch-all
Optional Catch-all Segments
Use [[...segment]] to make the catch-all optional. The route will also match the parent path.
Optional Catch-all Example
| URL | slug value |
|---|---|
| /shop | undefined |
| /shop/clothing | ['clothing'] |
| /shop/clothing/shirts | ['clothing', 'shirts'] |
Route Groups
Use parentheses (group) to organize routes without affecting the URL. Perfect for grouping related pages or applying shared layouts.
Route Group Example
Notice how (marketing), (shop), and (admin) don't appear in the URLs. They're purely for organization.
Route Groups with Layouts
Route groups are especially useful for applying different layouts to different sections:
layout.tsx that only applies to routes in that group.Private Folders
Prefix a folder with an underscore _folder to exclude it from routing. Perfect for components, utilities, and other non-route files.
Private Folder Example
Private folders can be placed anywhere in the app directory and are completely ignored by the router.
Ignored Patterns
- Folders starting with
_(underscore) - Folders starting with
.(dot) - Files starting with
_or. - The
api/directory (reserved for API routes)
Nearest Wins with Folders
loading.tsx, not-found.tsx, and error.tsx follow the folder hierarchy using "nearest wins" resolution. A file in a subfolder only affects that subfolder and shadows (without deleting) the same file in any ancestor folder.
Folder Hierarchy and Boundaries
- Each folder can define its own
loading.tsx,error.tsx, andnot-found.tsx - A file in a subfolder only affects routes inside that subfolder
- It shadows the same file in ancestor folders for routes in that subfolder
- Routes without a closer match fall through to the nearest ancestor
- If no file exists anywhere in the hierarchy, the built-in default is used
Example Structure
Resolution Flow
When a route needs a boundary file, the router checks:
- The route's own folder first
- Each parent folder (going up the hierarchy)
- The built-in default if no file is found
Route Priority
When multiple routes could match a URL, Bini.js resolves them in this order:
- Static routesexact matches — e.g.,
/about - Dynamic single segments
[slug]— e.g.,/blog/:slug - Catch-all segments
[...slug]— e.g.,/docs/* - Optional catch-all segments
[[...slug]]— e.g.,/shop/*(optional)
Priority Example
Consider this folder structure:
| URL | Matched Route |
|---|---|
| /blog | /blog (static) |
| /blog/hello-world | /blog/:slug (dynamic) |
| /blog/hello/world | /blog/* (catch-all) |
This ensures predictable routing behavior and prevents conflicts between different route types.
Complete Example
Here is a comprehensive folder structure showing all routing patterns:
Route Mapping
| Folder Path | URL | Type |
|---|---|---|
| app/page.tsx | / | Static |
| app/about/page.tsx | /about | Static |
| app/blog/page.tsx | /blog | Static |
| app/blog/[slug]/page.tsx | /blog/:slug | Dynamic |
| app/blog/authors/page.tsx | /blog/authors | Static |
| app/blog/categories/[...slug]/page.tsx | /blog/categories/* | Catch-all |
| app/docs/[[...slug]]/page.tsx | /docs/* | Optional Catch-all |
| app/api/hello.ts | /api/hello | API |
| app/api/users/[id].ts | /api/users/:id | API Dynamic |