Skip to content

oedotme/generouted

Repository files navigation


generouted


Generouted

Generated file-based routes forVite

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 ofclient-side file-based routing with React Routerwhich was packaged later asgenerouted.

Todaygeneroutedbrings 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?

generoutedusesVite's glob import APIto list the routes within thesrc/pagesdirectory and generates the routes tree and modals based ongenerouted'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 byVite
  • ✨ React support withreact-router-domor@tanstack/router🧪 or@tanstack/react-location🚨
  • ✨ Solid support with@solidjs/router
  • ✨ File-based MDX routes with React or Solid, requires@mdx-js/rollupinstallation 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, checkVite documentation to start a new project.

Installation

pnpm add @generouted/react-router react-router-dom

Setup

// vite.config.ts

import{defineConfig}from'vite'
importreactfrom'@vitejs/plugin-react'
importgeneroutedfrom'@generouted/react-router/plugin'

exportdefaultdefineConfig({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 filesrc/pages/index.tsx/,then export a default component:

exportdefaultfunctionHome(){
return<h1>Home</h1>
}

Check therouting conventions section below.

Docs

You can find more details about type-safe navigation and global modals at@generouted/react-routerdocs.

Examples


Solid Router

Solid Router

In case you don't have a Vite project with Solid and TypeScript, checkVite documentation to start a new project.

Installation

pnpm add @generouted/solid-router @solidjs/router

Setup

// vite.config.ts

import{defineConfig}from'vite'
importsolidfrom'vite-plugin-solid'
importgeneroutedfrom'@generouted/solid-router/plugin'

exportdefaultdefineConfig({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 filesrc/pages/index.tsx/,then export a default component:

exportdefaultfunctionHome(){
return<h1>Home</h1>
}

See more aboutgeneroutedrouting conventions below.

Docs

You can find more details about type-safe navigation and global modals at@generouted/solid-routerdocs.

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, checkVite 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 filesrc/pages/index.tsx/,then export a default component:

exportdefaultfunctionHome(){
return<h1>Home</h1>
}

Examples



Conventions

File and directories naming and conventions

  • Routes declaration atsrc/pages
  • Supports.tsx,.jsxand.mdxfile extensions
  • Optionalsrc/pages/_app.tsxfor anapp level layout
  • Optionalsrc/pages/404.tsxfor acustom 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.tsxin any nested directory →src/pages/posts/_layout.tsx
  • Requiresusing an<Outlet />component to render layout children
  • All the files within thesrc/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
  • Includedots.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 withparentheses()
  • src/pages/(auth)/_layout.tsx
  • src/pages/(auth)/login.tsx/login
  • Layout parent directory name is not included in the routes paths

Global modals

  • Byprefi xingthe file name with aplus sign+(thinking the modal is an extra route overlaying the current route)
  • Modals navigation available via theuseModals()hook
  • src/pages/+info.tsx/info
  • src/pages/+checkout/+details.tsx/checkout/details
  • src/pages/+checkout/+payment.tsx/checkout/payment

Optional segments

  • Byprefi xinga route segment with aminus 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 anunderscore_will beignored
  • src/pages/_ignored.tsx
  • src/pages/posts/_components/button.tsx
  • src/pages/posts/_components/link.tsx

Page exports

  • Requiredpage componentexport default Component() {...}
  • Optional page loader functionexport const Loader = () => {...}
  • Optional page action functionexport const Action = () => {...}
  • Optional suspense-based pending componentexport const Pending = () => {...}
  • Optional error boundary componentexport 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/blogroutes
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 * Custom404(optional)
_app.tsx Customapplayout(optional)
_ignored.tsx Ignored route
about.tsx /about Regular route
blog.w.o.layout.tsx /blog/w/o/layout Route without/bloglayout
index.tsx / Index route

API

Routing

Via@generouted/react-routeror@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/lazyor@generouted/solid-router/lazy

  • Used instead of@generouted/react-routeror@generouted/solid-routerto enable lazy-loading
  • Make sure to replace all imports to lazy imports — namely at app entry andsrc/pages/_app.tsx
  • Provides the same<Routes />androutesexports

Plugins

Via@generouted/react-router/pluginor@generouted/solid-router/plugin

  • Vite plugin for type generation and initializing type-safe components/hooks/utils
  • Generatessrc/router.tsfile
  • Exports type-safe<Link>,<Navigate>,useModals(),useNavigate(),useParams(),redirect(),etc.
  • Check out@generouted/react-routerdocsor@generouted/solid-routerdocsfor more details

Core

Via@generouted/react-router/coreor@generouted/solid-router/core

  • Available for customization, however it's recommended to use the available integrations directory via the<Routes/>component
  • Check out thecustom 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:

// src/config/redirects.tsx

import{Navigate,useLocation}from'react-router-dom'

import{useAuth}from'../context/auth'
import{Path}from'../router'

constPRIVATE:Path[]=['/logout']
constPUBLIC:Path[]=['/login']

exportconstRedirects=({children}:{children:React.ReactNode})=>{
constauth=useAuth()
constlocation=useLocation()

constauthenticatedOnPublicPath=auth.active&&PUBLIC.includes(location.pathnameasPath)
constunAuthenticatedOnPrivatePath=!auth.active&&PRIVATE.includes(location.pathnameasPath)

if(authenticatedOnPublicPath)return<Navigateto="/"replace/>
if(unAuthenticatedOnPrivatePath)return<Navigateto="/login"replace/>

returnchildren
}

Then use that component (<Redirects>) at the root-level layoutsrc/pages/_app.tsxto wrap the<Outlet>component:

// src/pages/_app.tsx

import{Outlet}from'react-router-dom'

import{Redirects}from'../config/redirects'

exportdefaultfunctionApp(){
return(
<section>
<header>
<nav>...</nav>
</header>

<main>
<Redirects>
<Outlet/>
</Redirects>
</main>
</section>
)
}

You can find a full example of this approach atRender template


How to use with Hash or Memory Routers?

You can use the exportedroutesobject to customize the router or to use hash/memory routers:

import{createRoot}from'react-dom/client'
import{RouterProvider,createHashRouter}from'react-router-dom'
import{routes}from'@generouted/react-router'

constrouter=createHashRouter(routes)
constRoutes=()=><RouterProviderrouter={router}/>

createRoot(document.getElementById('root')!).render(<Routes/>)


License

MIT