Project structure
The folders and files in a Bini.js project, and how they turn into routes.
Overview
Everything the router reads lives in src/app. Add a file there and you get a page. Add a file to src/app/api and you get an endpoint. There is no router config to write.
Default project layout
This is the src folder created by npx create-bini-app@latest.
| Path | What it does |
|---|---|
| src/app | Your routes and layouts. The router scans this folder. |
| src/app/api | API handlers. hello.ts is served at /api/hello. |
| src/app/layout.tsx | Root layout. Wraps every page and holds global metadata. |
| src/app/page.tsx | The home page, served at /. |
| src/app/globals.css | Global styles imported by the root layout. |
| src/main.tsx | App entry point. Also exports render() used for static site generation. |
| src/App.tsx | Route tree generated by bini-router. It is rewritten when routes change, so do not edit it. |
| public | Static assets served from the site root, such as favicon.ico and site.webmanifest. |
Top-level files
Files in the project root configure tooling. None of them affect routing.
| File | Purpose |
|---|---|
| vite.config.ts | Vite configuration. Registers bini-router, bini-env and the other plugins. |
| package.json | Dependencies and scripts such as dev, build and deploy. |
| .env | Environment variables. BINI_* is exposed to the browser, everything else stays on the server. |
| .oxlintrc.json | Oxlint configuration. |
| .oxfmtrc.json | Oxfmt configuration. |
Routing files
Some file names have a fixed job inside src/app. A routing file applies to its own folder and every folder below it. A file in a subfolder overrides the same file from a parent, and if none exists Bini.js falls back to a built-in default.
<Layout>
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Loading />}>
<Template>
<Page />
</Template>
</Suspense>
</ErrorBoundary>
</Layout>These files wrap a page in a fixed order. The layout is outermost. Inside it, an error boundary catches errors and shows error.tsx, and a Suspense boundary shows loading.tsx while the page loads. The template wraps the page itself, and the page is the innermost piece. not-found.tsx is not part of this wrapper. It renders when no route matches, inside the same layout chain.
| File | Purpose |
|---|---|
| page.tsx | The UI for a route. Can also be .mdx or .md. |
| layout.tsx | Shared UI for a folder and its children. Renders children through <Outlet />. |
| template.tsx | Like a layout, but wraps each page individually. |
| loading.tsx | Fallback shown while a page or layout loads. |
| error.tsx | Fallback shown when something in the folder throws. |
| not-found.tsx | UI for URLs that match no route under the folder. |
| default.tsx | Fallback for a parallel route slot with no match. |
page files can be MDX or Markdown. Every other routing file must be .tsx, .jsx, .ts or .js.Folders and files become routes
Folders define URL segments, and nesting folders nests segments. A file becomes a route in two ways: a page file inside a folder, or a flat file placed directly in a folder. A file named index is treated the same as page and maps to its parent folder.
A page.tsx in the root is the home page. A flat file such as about.tsx becomes /about without needing its own folder. Inside blog, page.tsx maps to /blog, and [slug].tsx is a dynamic route where :slug matches any value. A layout.tsx has no URL of its own. It wraps its folder's routes and everything below them.
Colocation and private folders
You can keep components, helpers and data files next to the routes that use them. Anything starting with _ or . is skipped by the router, along with everything inside it. Prefix a folder with an underscore, such as _components, to mark it as a private implementation detail.
Files inside a private folder, like button.tsx and constants.ts, never produce a URL. The same goes for files with an underscore prefix, like _nav.tsx and _db.ts. The routable files next to them, page.tsx and hello.ts, keep working as normal, so a route can hold its own helpers without exposing them.
Route groups
Wrap a folder name in parentheses, such as (marketing), to organize routes without changing their URLs. The group name is dropped from the path, and the group can have its own layout, loading and error files.
Because the group name is dropped, the pages get plain URLs: /about and /cart. Each group's layout.tsx only wraps the routes inside that group, so marketing pages and shop pages can share one URL space while looking completely different.
Organizing your project
Bini.js does not enforce where non-route code lives. Pick one approach and stay consistent across the project. The folder names below, components and lib, are placeholders with no special meaning.
Store shared code outside src/app
Keep all shared code in folders next to app and use src/app only for routing. Nothing here can become a route by accident.
Split code by route with private folders
Keep code that only one route uses inside that route's folder, in _ folders. Shared code can stay in the folders from the first approach.