Generouted
Generated file-based routes for Vite
<summary>Motivation</summary>
I enjoyed using file-based routing since I tried Next.js (pages directory). After applying the same concept with Vite and client-side applications, I started writing blog posts covering the implementation of client-side file-based routing with React Router which was packaged later as
I enjoyed using file-based routing since I tried Next.js (pages directory). After applying the same concept with Vite and client-side applications, I started writing blog posts covering the implementation of client-side file-based routing with React Router which was packaged later as
generouted
.
Today generouted
brings many features, supports multiple frameworks and routers, and inspires ideas and conventions from Next.js, Remix, Expo, Docusaurus, SvelteKit and more.
<summary>How does it work?</summary>
generouted
uses Vite's glob import API to list the routes within the src/pages
directory and generates the routes tree and modals based on generouted
's conventions.
There are also Vite plugins available for some integrations to provide type-safe components/hooks/utils through code-generation.
Features
- 📁 Client-side file-based routing
- ⚡ Powered by Vite
- ✨ React support with
react-router
or@tanstack/router
🧪 or@tanstack/react-location
🚨 - ✨ Solid support with
@solidjs/router
- ✨ File-based MDX routes with React or Solid, requires
@mdx-js/rollup
installation and setup - 🔐 Type-safe navigation
- 🚀 Type-safe global modals
- 💤 Route-based code-splitting and lazy-loading
- 📄 Route-based data loaders and actions
- 💣 Route-based error boundary
- 📂 Nested layouts
- 🫙 Pathless layout groups
- ❓ Optional static and dynamic routes
- 💭 Ignored routes per file or directory
Online explorer
- ⚡ Visit
generouted
's interactive playground via StackBlitz - 🧩 Explore file-based routing patterns and conventions
- 🔎 Visualize the routes layouts and the resolved routes paths
- 📝 Update
src/pages/
files and see your changes reflecting
Getting started
<summary>React Router</summary>
### React Router
In case you don't have a Vite project with React and TypeScript, check Vite documentation to start a new project.
#### Installation
shell
pnpm add @generouted/react-router react-router
#### Setup
ts
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import generouted from '@generouted/react-router/plugin'
export default defineConfig({ plugins: [react(), generouted()] })
#### Usage
tsx
// src/main.tsx
import { createRoot } from 'react-dom/client'
import { Routes } from '@generouted/react-router'
createRoot(document.getElementById('root')!).render(<Routes />)
#### Adding pages
Add the home page by creating a new file src/pages/index.tsx
→ /
, then export a default component:
tsx
export default function Home() {
return <h1>Home</h1>
}
Check the routing conventions section below.
#### Docs
You can find more details about type-safe navigation and global modals at @generouted/react-router
docs.
#### Examples
- Type-safe navigation + global modals
- Custom integration
- Custom integration with custom path
- MDX routes
<summary>Solid Router</summary>
### Solid Router
In case you don't have a Vite project with Solid and TypeScript, check Vite documentation to start a new project.
#### Installation
shell
pnpm add @generouted/solid-router @solidjs/router
#### Setup
ts
// vite.config.ts
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'
import generouted from '@generouted/solid-router/plugin'
export default defineConfig({ plugins: [solid(), generouted()] })
#### Usage
tsx
// src/main.tsx
import { render } from 'solid-js/web'
import { Routes } from '@generouted/solid-router'
render(Routes, document.getElementById('root')!)
#### Adding pages
Add the home page by creating a new file src/pages/index.tsx
→ /
, then export a default component:
tsx
export default function Home() {
return <h1>Home</h1>
}
See more about generouted
routing conventions below.
#### Docs
You can find more details about type-safe navigation and global modals at @generouted/solid-router
docs.
#### Examples
- Type-safe navigation + global modals
<summary>TanStack React Router — In-progress experimental support 🧪</summary>
### TanStack React Router — In-progress experimental support 🧪
Check out the docs here
#### Examples
- Basic
<summary>React Location — Deprecated 🚨</summary>
### React Location — Deprecated 🚨
In case you don't have a Vite project with React and TypeScript, check Vite documentation to start a new project.
#### Installation
shell
pnpm add generouted @tanstack/react-location
#### Usage
tsx
// src/main.tsx
import { createRoot } from 'react-dom/client'
import { Routes } from 'generouted/react-location'
createRoot(document.getElementById('root')!).render(<Routes />)
#### Adding pages
Add the home page by creating a new file src/pages/index.tsx
→ /
, then export a default component:
tsx
export default function Home() {
return <h1>Home</h1>
}
#### Examples
- Basic
- Data loaders
- Modals
- Nested layouts
Conventions
File and directories naming and conventions
- Routes declaration at
src/pages
- Supports
.tsx
,.jsx
and.mdx
file extensions - Optional
src/pages/_app.tsx
for an app level layout - Optional
src/pages/404.tsx
for a custom not-found page
Index routes
src/pages/index.tsx
→/
src/pages/posts/index.tsx
→/posts
Nested routes
src/pages/posts/2022/index.tsx
→/posts/2022
src/pages/posts/2022/resolutions.tsx
→/posts/2022/resolutions
Dynamic routes
src/pages/posts/[slug].tsx
→/posts/:slug
src/pages/posts/[slug]/tags.tsx
→/posts/:slug/tags
src/pages/posts/[...all].tsx
→/posts/*
Nested layouts
- By defining
_layout.tsx
in any nested directory →src/pages/posts/_layout.tsx
- Requires using an
<Outlet />
component to render layout children - All the files within the
src/pages/posts/
directory in this case, will be wrapped with that layout
Nested URLs without nested layouts
- Route file should be outside of the nested layout directory
- Include dots
.
between the segments to be converted to forward slashes src/pages/posts.nested.as.url.not.layout.tsx
→/posts/nested/as/url/not/layout
Pathless layouts
- Similar to nested layouts for layout definition
- By wrapping the parent directory with parentheses
()
src/pages/(auth)/_layout.tsx
src/pages/(auth)/login.tsx
→/login
- Layout parent directory name is not included in the routes paths
Global modals
- By prefixing the file name with a plus sign
+
(thinking the modal is an extra route overlaying the current route) - Modals navigation available via the
useModals()
hook src/pages/+info.tsx
→/info
src/pages/+checkout/+details.tsx
→/checkout/details
src/pages/+checkout/+payment.tsx
→/checkout/payment
Optional segments
- By prefixing a route segment with a minus sign
-
(thinking the segment can be subtracted or removed from the route path) src/pages/-en/about.tsx
→/en?/about
→/en/about
,/about
src/pages/-[lang]/about.tsx
→/:lang?/about
→/en/about
,/fr/about
,/about
Ignored routes
- Any directory or file starts with an underscore
_
will be ignored src/pages/_ignored.tsx
src/pages/posts/_components/button.tsx
src/pages/posts/_components/link.tsx
Page exports
- Required page component
export default Component() {...}
- Optional page loader function
export const Loader = () => {...}
- Optional page action function
export const Action = () => {...}
- Optional suspense-based pending component
export const Pending = () => {...}
- Optional error boundary component
export const Catch = () => {...}
Example
<summary>Directory structure</summary>
shell
src/pages
├── (auth)
│ ├── _layout.tsx
│ ├── login.tsx
│ └── register.tsx
├── blog
│ ├── _components
│ │ ├── button.tsx
│ │ └── comments.tsx
│ ├── [...all].tsx
│ ├── [slug].tsx
│ ├── _layout.tsx
│ ├── index.tsx
│ └── tags.tsx
├── docs
│ ├── -[lang]
│ │ ├── index.tsx
│ │ └── resources.tsx
│ └── -en
│ └── contributors.tsx
├── +info.tsx
├── 404.tsx
├── _app.tsx
├── _ignored.tsx
├── about.tsx
├── blog.w.o.layout.tsx
└── index.tsx
<summary>Overview</summary>
| File | Path | Convention | | :------------------------------ | :----------------------- | :------------------------------------ | |
| File | Path | Convention | | :------------------------------ | :----------------------- | :------------------------------------ | |
(auth)/_layout.tsx
| | Pathless Layout group |
| (auth)/login.tsx
| /login
| Regular route |
| (auth)/register.tsx
| /register
| Regular route |
| blog/_components/button.tsx
| | Ignored route by an ignored directory |
| blog/_components/comments.tsx
| | Ignored route by an ignored directory |
| blog/[...all].tsx
| /blog/*
| Dynamic catch-all route |
| blog/[slug].tsx
| /blog/:slug
| Dynamic route |
| blog/_layout.tsx
| | Layout for /blog
routes |
| blog/index.tsx
| /blog
| Index route |
| blog/tags.tsx
| /blog/tags
| Regular route |
| docs/-[lang]/index.tsx
| /docs/:lang?/index
| Optional dynamic route segment |
| docs/-[lang]/resources.tsx
| /docs/:lang?/resources
| Optional dynamic route segment |
| docs/-en/contributors.tsx
| /docs/en?/contributors
| Optional static route segment |
| +info.tsx
| /info
| Modal route |
| 404.tsx
| *
| Custom 404
(optional) |
| _app.tsx
| | Custom app
layout (optional) |
| _ignored.tsx
| | Ignored route |
| about.tsx
| /about
| Regular route |
| blog.w.o.layout.tsx
| /blog/w/o/layout
| Route without /blog
layout |
| index.tsx
| /
| Index route |
API
Routing
Via @generouted/react-router
or @generouted/solid-router
<Routes />
— file-based routing component to be render in the app entryroutes
— file-based routes tree/object used by default at<Routes />
component
Routing + code-splitting and lazy-loading
Via @generouted/react-router/lazy
or @generouted/solid-router/lazy
- Used instead of
@generouted/react-router
or@generouted/solid-router
to enable lazy-loading - Make sure to replace all imports to lazy imports — namely at app entry and
src/pages/_app.tsx
- Provides the same
<Routes />
androutes
exports
Plugins
Via @generouted/react-router/plugin
or @generouted/solid-router/plugin
- Vite plugin for type generation and initializing type-safe components/hooks/utils
- Generates
src/router.ts
file - Exports type-safe
<Link>
,<Navigate>
,useModals()
,useNavigate()
,useParams()
,redirect()
, etc. - Check out
@generouted/react-router
docs or@generouted/solid-router
docs for more details
Core
Via @generouted/react-router/core
or @generouted/solid-router/core
- Available for customization, however it's recommended to use the available integrations directory via the
<Routes/>
component - Check out the custom integration example
FAQ
<summary>How to implement protected or guarded routes?</summary>
There are multiple approaches to achieve that. If you prefer handling the logic in one place, you can define the protected routes with redirection handling within a component:
There are multiple approaches to achieve that. If you prefer handling the logic in one place, you can define the protected routes with redirection handling within a component:
tsx
// src/config/redirects.tsx
import { Navigate, useLocation } from 'react-router'
import { useAuth } from '../context/auth'
import { Path } from '../router'
const PRIVATE: Path[] = ['/logout']
const PUBLIC: Path[] = ['/login']
export const Redirects = ({ children }: { children: React.ReactNode }) => {
const auth = useAuth()
const location = useLocation()
const authenticatedOnPublicPath = auth.active && PUBLIC.includes(location.pathname as Path)
const unAuthenticatedOnPrivatePath = !auth.active && PRIVATE.includes(location.pathname as Path)
if (authenticatedOnPublicPath) return <Navigate to="/" replace />
if (unAuthenticatedOnPrivatePath) return <Navigate to="/login" replace />
return children
}
Then use that component (<Redirects>
) at the root-level layout src/pages/_app.tsx
to wrap the <Outlet>
component:
tsx
// src/pages/_app.tsx
import { Outlet } from 'react-router'
import { Redirects } from '../config/redirects'
export default function App() {
return (
<section>
<header>
<nav>...</nav>
</header>
<main>
<Redirects>
<Outlet />
</Redirects>
</main>
</section>
)
}
You can find a full example of this approach at Render template
<summary>How to use with Hash or Memory Routers?</summary>
You can use the exported
You can use the exported
routes
object to customize the router or to use hash/memory routers:
tsx
import { createRoot } from 'react-dom/client'
import { RouterProvider, createHashRouter } from 'react-router'
import { routes } from '@generouted/react-router'
const router = createHashRouter(routes)
const Routes = () => <RouterProvider router={router} />
createRoot(document.getElementById('root')!).render(<Routes />)
License
MIT