File-Based Routing
Learn how special files like page.tsx, layout.tsx, loading.tsx, error.tsx, and MDX pages define route behavior in Bini.js.
Overview
Bini.js uses a file-based routing system where files in the src/app/ directory automatically become routes in your application. Each file has a specific purpose and is automatically recognized by the router.
Routes are automatically generated from your file structure
Full support for both .tsx and .jsx files
Content pages work out of the box with .mdx and .md
Create shared UI that persists across navigation
Special Files
Bini.js recognizes these special files in the src/app/ directory:
| File | Purpose | Required |
|---|---|---|
| page.tsx / page.jsx | Defines a public route — required to make a route accessible | ✅ Yes |
| page.mdx / page.md | MDX/Markdown content route — full JSX/import/export support | ❌ No |
| layout.tsx / layout.jsx | Shared UI that wraps pages and nested layouts | ✅ Yes (root) |
| loading.tsx / loading.jsx | Loading UI shown while page content streams | ❌ No |
| error.tsx / error.jsx | Error UI when something breaks in a route or its children | ❌ No |
| not-found.tsx / not-found.jsx | Custom 404 page for unmatched routes | ❌ No |
_ or . are ignored by the router. The api/ directory is excluded from page route scanning.page.tsx / page.jsx
The page.tsx file defines a public route. Without it, the folder is not accessible via URL. Each page.tsx must have a default export of a React component.
Basic Pages
Flat File Pages
Pages can also be defined as flat files without a folder:
This creates routes at /about and /contact without needing separate folders.
Auto-Imports
Bini.js automatically injects imports into every page and layout file under src/app/ (excluding src/app/api/). You never need to write import statements for these:
MDX & Markdown Pages
Bini.js supports .mdx and .md files as content routes out of the box — no setup required. @mdx-js/rollup is bundled internally.
MDX Page Example
MDX with Imports
Flat File MDX Routes
Both .mdx and .md are compiled through the same MDX pipeline (full JSX/import/export support in both). Auto-imports apply to MDX files the same as any other page.
layout.tsx, not-found.tsx, loading.tsx, and error.tsx must stay .tsx/.jsx — they define app structure rather than content.Extension Priority
When multiple files share the same base name (e.g., both page.tsx and page.mdx exist in the same folder):
.tsx > .jsx > .ts > .js > .mdx > .mdThe higher-priority file wins; the lower-priority one is simply ignored for that route.
layout.tsx / layout.jsx
Layouts wrap pages and other layouts, providing shared UI that persists across navigation. All layouts are rendered as React Router <Route element> wrappers using <Outlet />.
Root Layout
The root layout at src/app/layout.tsx is required. It wraps all pages in your application and can export metadata for the entire app.
Nested Layout
Create layouts for specific sections by adding layout.tsx in subdirectories.
Layout Nesting
The root layout wraps the dashboard layout, which wraps the settings page.
Layout Metadata
Export metadata from any layout. Root layout metadata is injected into index.html at build time. Nested layout titles update document.title at runtime.
loading.tsx / loading.jsx
The loading.tsx file provides a loading UI while page content is being loaded. It wraps the page in a Suspense boundary.
Global Loading
Route-Specific Loading
The loading UI is shown immediately on navigation while the page content streams in. If no loading.tsx exists, a built-in dark-mode-aware spinner is used automatically.
error.tsx / error.jsx
The error.tsx file catches errors thrown anywhere in a route or its children. It wraps the route and its children in an Error Boundary.
Error Component
Error Props
Your error.tsx component receives two props:
error— The thrown Error object with message and stack tracereset— A function that clears the error state and re-renders children
Folder-Scoped Errors
Place error.tsx in any folder to catch errors only for that route and its children:
__bini_error__ CustomEvent on window. In production, generic "Something went wrong" UI is shown if no error.tsx exists.not-found.tsx / not-found.jsx
The not-found.tsx file defines a custom 404 page for unmatched routes.
Custom 404 Page
Programmatic 404
You can also trigger the 404 page programmatically:
Scoped Not Found
Nearest Wins Resolution
loading.tsx, not-found.tsx, and error.tsx all use "nearest wins" resolution — a file in a subfolder only affects that subfolder and shadows (without deleting) the same file in any ancestor folder.
How It Works
- A file in a subfolder only affects routes inside that subfolder
- It shadows (but doesn't delete) the same file in ancestor folders
- Routes without a closer match fall through to the nearest ancestor
- Built-in defaults apply if nothing exists anywhere
Example Structure
Resolution Flow
When a route needs a boundary file (loading, error, or not-found):
- Check the route's own folder first
- If not found, check each parent folder (going up)
- If still not found, use the built-in default
Built-in Defaults
- Loading: Built-in dark-mode-aware spinner
- Error:
nullin dev (Vite overlay takes over), generic "Something went wrong" in production - Not Found: Built-in 404 page
File Combinations
Special files can be combined in the same folder to create rich route behavior:
| Route | Files Used |
|---|---|
| /dashboard | layout.tsx + loading.tsx + error.tsx + page.tsx |
| /dashboard/settings | layout.tsx + loading.tsx (from settings) + error.tsx (from dashboard) + page.tsx |
| /dashboard/profile | layout.tsx + profile/layout.tsx + loading.tsx + error.tsx + page.tsx |
File Priority
When multiple files could apply to a route, they are resolved in this order (from outermost to innermost):
- Root
layout.tsx - Nested
layout.tsxfiles (from root to leaf) loading.tsx(closest to the page)error.tsx(closest to the page)not-found.tsx(if triggered)page.tsxorpage.mdx
Dynamic Routes
Create dynamic routes using [param] syntax in folder or file names.
Dynamic Segment
Flat File Dynamic Routes
Using Params
Catch-All Routes
Use [...param] syntax to match multiple path segments.
Catch-All Example
Route Priority
Routes are matched in this order:
- Static routes (e.g.,
/about) - Dynamic routes (e.g.,
/blog/:slug) - Catch-all routes (e.g.,
/docs/*)
Routes are sorted by priority and then by path length (shortest first).
API Routes
Write your API files in src/app/api/. Handlers can be either a .fetch(request)-style app or a plain function handler.
Hono App (Recommended)
Plain Function Handler
Dynamic API Routes
API Route Structure
/api prefix — Bini.js strips it before your handler sees the request. Requires npm install hono if you choose the Hono style.Complete Example
Here's a comprehensive file structure showing all special files:
Route Mapping
| File Path | URL | Type |
|---|---|---|
| app/page.tsx | / | Static |
| app/about.mdx | /about | MDX Page |
| app/blog/page.tsx | /blog | Static |
| app/blog/[slug]/page.tsx | /blog/:slug | Dynamic |
| app/dashboard/page.tsx | /dashboard | Static |
| app/dashboard/settings/page.tsx | /dashboard/settings | Static |
| app/dashboard/profile/page.tsx | /dashboard/profile | Static |
| app/docs/[...path]/page.tsx | /docs/* | Catch-all |
| app/api/hello.ts | /api/hello | API |
| app/api/users/[id].ts | /api/users/:id | API Dynamic |