Repository files navigation
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 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 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
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 .
Installation
pnpm add @generouted/react-router react-router
Setup
// 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
)">// 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:
export default function Home ( ) { return < h1 > Homeh1 > }
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
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 .
Installation
pnpm add @generouted/solid-router @solidjs/router
Setup
// 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
// 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:
export default function Home ( ) { return < h1 > Homeh1 > }
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
TanStack React Router -- In-progress experimental support
TanStack React Router -- In-progress experimental support
Check out the docs here
Examples
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 .
Installation
pnpm add generouted @tanstack/react-location
Usage
)">// 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:
export default function Home ( ) { return < h1 > Homeh1 > }
Examples
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 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
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
-- file-based routing component to be render in the app entry
routes -- file-based routes tree/object used by default at 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 and routes exports
Plugins
Via @generouted/react-router/plugin or @generouted/solid-router/plugin
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 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:
{
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
}">// 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 ( ) at the root-level layout src/pages/_app.tsx to wrap the component:
)
}">// 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
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:
createRoot(document.getElementById('root')!).render( )">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