Generated client-side file-based routes for Vite
npm install generoutedGenerated 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.
- ๐ 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
- โก 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
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(
`
#### 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.
#### 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
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 Home
}
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
TanStack React Router โ In-progress experimental support ๐งช
#### Examples
- Basic
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(
`
#### 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
- Data loaders
- Modals
- Nested layouts
- Routes declaration at src/pages.tsx
- Supports , .jsx and .mdx file extensionssrc/pages/_app.tsx
- Optional for an app level layoutsrc/pages/404.tsx
- Optional 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/2022src/pages/posts/2022/resolutions.tsx
- โ /posts/2022/resolutions
#### Dynamic routes
- src/pages/posts/[slug].tsx โ /posts/:slugsrc/pages/posts/[slug]/tags.tsx
- โ /posts/:slug/tagssrc/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 childrensrc/pages/posts/
- All the files within the 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 slashessrc/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)_useModals()
- Modals navigation available via the hooksrc/pages/+info.tsx
- โ /infosrc/pages/+checkout/+details.tsx
- โ /checkout/detailssrc/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, /aboutsrc/pages/-[lang]/about.tsx
- โ /:lang?/about โ /en/about, /fr/about, /about
#### Ignored routes
- Any directory or file starts with an underscore _ will be ignoredsrc/pages/_ignored.tsx
- src/pages/posts/_components/button.tsx
- src/pages/posts/_components/link.tsx
-
- Required page component export default Component() {...}export const Loader = () => {...}
- Optional page loader function export const Action = () => {...}
- Optional page action function export const Pending = () => {...}
- Optional suspense-based pending component export const Catch = () => {...}
- Optional error boundary component
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 |
Via @generouted/react-router or @generouted/solid-router
- โ file-based routing component to be render in the app entryroutes
- โ file-based routes tree/object used by default at component
Via @generouted/react-router/lazy or @generouted/solid-router/lazy
- Used instead of @generouted/react-router or @generouted/solid-router to enable lazy-loadingsrc/pages/_app.tsx
- Make sure to replace all imports to lazy imports โ namely at app entry and
- Provides the same and routes exports
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 , , useModals(), useNavigate(), useParams(), redirect(), etc.@generouted/react-router
- Check out docs or @generouted/solid-router docs for more details
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
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'
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'
import { Redirects } from '../config/redirects'
export default function App() {
return (
)
}
`
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:
`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 = () =>
createRoot(document.getElementById('root')!).render(
``
MIT