Generouted
Generated file-based routes for Vite
Motivation
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](https://omarelhawary.me/blog/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.
How does it work?
`generouted` uses [Vite's glob import API](https://vitejs.dev/guide/features.html#glob-import) 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-dom
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
Getting started
React Router
### React Router
In case you don't have a Vite project with React and TypeScript, check [Vite documentation to start a new project](https://vitejs.dev/guide/#scaffolding-your-first-vite-project).
#### Installation
```shell
pnpm add @generouted/react-router react-router-dom
```
#### 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()
```
#### 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 Home
}
```
Check the [routing conventions section below](#conventions).
#### Docs
You can find more details about type-safe navigation and global modals at [`@generouted/react-router` docs](/packages/react-router).
#### Examples
- [Type-safe navigation + global modals](/examples/react-router)
- [Custom integration](/examples/react-router-custom)
- [Custom integration with custom path](/examples/react-router-custom-path)
- [MDX routes](/examples/react-router-mdx)
Solid Router
### Solid Router
In case you don't have a Vite project with Solid and TypeScript, check [Vite documentation to start a new project](https://vitejs.dev/guide/#scaffolding-your-first-vite-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 Home
}
```
See more about `generouted` [routing conventions below](#conventions).
#### Docs
You can find more details about type-safe navigation and global modals at [`@generouted/solid-router` docs](/packages/solid-router).
#### Examples
- [Type-safe navigation + global modals](/examples/solid-router)
TanStack React Router โ In-progress experimental support ๐งช
### TanStack React Router โ In-progress experimental support ๐งช
[Check out the docs here](/packages/tanstack-react-router)
#### Examples
- [Basic](/examples/tanstack-react-router)
React Location โ Deprecated ๐จ
### React Location โ Deprecated ๐จ
In case you don't have a Vite project with React and TypeScript, check [Vite documentation to start a new project](https://vitejs.dev/guide/#scaffolding-your-first-vite-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()
```
#### 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 Home
}
```
#### Examples
- [Basic](/examples/react-location/basic)
- [Data loaders](/examples/react-location/data-loaders)
- [Modals](/examples/react-location/modals)
- [Nested layouts](/examples/react-location/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
Directory structure
```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
```
Overview
| 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 entry
routes
โ 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 />
and routes
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
How to implement protected or guarded routes?
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-dom'
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
if (unAuthenticatedOnPrivatePath) return
return children
}
```
Then use that component (`` ) at the root-level layout `src/pages/_app.tsx` to wrap the `` component:
```tsx
// src/pages/_app.tsx
import { Outlet } from 'react-router-dom'
import { Redirects } from '../config/redirects'
export default function App() {
return (
)
}
```
You can find a full example of this approach at [Render template](https://github.com/oedotme/render/blob/main/src/config/redirects.tsx)
How to use with Hash or Memory Routers?
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-dom'
import { routes } from '@generouted/react-router'
const router = createHashRouter(routes)
const Routes = () =>
createRoot(document.getElementById('root')!).render()
```
License
MIT