Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/oedotme/generouted

Generated file-based routes for Vite
https://github.com/oedotme/generouted

actions code-splitting data-loaders file-based-routing generate nested-layouts nextjs pages pre-loading react react-location react-router react-router-dom remix router routes solid solid-router typescript vite

Last synced: 23 days ago
JSON representation

Generated file-based routes for Vite

Awesome Lists containing this project

README

        



generouted












# Generouted

Generated file-based routes for [Vite](https://vitejs.dev)

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](https://vitejs.dev)
- โœจ React support with [`react-router-dom`](https://github.com/remix-run/react-router) or [`@tanstack/router`](https://github.com/tanstack/router) ๐Ÿงช or [`@tanstack/react-location`](https://github.com/tanstack/router/tree/9c8eb043e4ac350fc1d28655542e01defb0c82e5) ๐Ÿšจ
- โœจ Solid support with [`@solidjs/router`](https://github.com/solidjs/solid-router)
- โœจ File-based MDX routes with React or Solid, requires [`@mdx-js/rollup`](https://mdxjs.com/packages/rollup) [installation and setup](/examples/react-router-mdx)
- ๐Ÿ” 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](https://stackblitz.com/github.com/oedotme/generouted/tree/main/explorer)
- ๐Ÿงฉ 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

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 `` 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`](/packages/react-router) or [`@generouted/solid-router`](/packages/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`

- 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.
- Check out [`@generouted/react-router` docs](/packages/react-router) or [`@generouted/solid-router` docs](/packages/solid-router) 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 `` component
- Check out the [custom integration example](/examples/react-router-custom)


## 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