generouted

repository·main·Indexed 22 days ago

https://github.com/oedotme/generouted

A file-based routing library for Vite-powered client-side applications that brings developer experience patterns similar to Next.js and Remix. It supports multiple frameworks, including React (via react-router) and Solid (via @solidjs/router), and provides features such as type-safe navigation, nested layouts, pathless layout groups, global modals, and MDX support. Generouted leverages Vite's glob import API to automatically generate a routes tree based on the file structure in the src/pages directory.

Tokens
15.7K
Snippets
43
Records
86
Agent score
78%

What's inside generouted

  1. Generouted Features Overview

    main

    Generouted provides a comprehensive suite of file-based routing features including:

    • Client-side routing: Powered by Vite.
    • Framework Support: React (react-router, @tanstack/router 🧪, @tanstack/react-location 🚨) and Solid (@solidjs/router).
    • MDX Support: File-based MDX routes (requires @mdx-js/rollup).
    • Type-safety: Type-safe navigation and global modals.
    • Performance: Route-based code-splitting and lazy-loading.
    • Advanced Routing: Nested layouts, pathless layout groups, optional static/dynamic routes, and route-based data loaders, actions, and error boundaries.
    • Control: Ability to ignore specific routes per file or directory.
  2. Configure nested layouts

    main

    To define a layout for a specific directory of routes, create a _layout.tsx file within that directory.

    Requirements:

    • You must use an <Outlet /> component within the layout to render the child routes.
    • All files within that directory will be wrapped by this layout.

    Example: src/pages/posts/_layout.tsx will wrap all routes inside src/pages/posts/.

  3. How file-based routing works in Generouted

    main

    Generouted uses a file-based routing convention. Files created in src/pages/ map to URL paths. For example, src/pages/index.tsx maps to /. Each page file must export a default component.

    To create a root layout that wraps all pages, create a file at src/pages/_app.tsx. This component receives props.children which contains the rendered page content.

    // src/pages/index.tsx
    
    export default function Home() {
      return <h1>Home</h1>
    }
    
    // src/pages/_app.tsx
    
    import { ParentProps } from 'solid-js'
    
    export default function App(props: ParentProps) {
      return (
        <section>
          <header>
            <nav>...</nav>
          </header>
    
          <main>{props.children}</main>
        </section>
      )
    }
  4. How Generouted works

    main
    Generouted leverages Vite's glob import API to scan the src/pages directory. It automatically generates a routes tree and handles modals based on predefined file and directory naming conventions. For enhanced developer experience, specific Vite plugins are available for different frameworks to provide type-safe components, hooks, and utilities through code generation.
  5. How type-safe global modals work

    main

    Generouted allows you to create global modals by prefixing a route file name with a plus sign + (e.g., src/pages/+login.tsx). These modals overlay the current route.

    To control modals, use the useModals hook exported from src/router.ts. The open and close methods are type-safe and support an at option to specify which route the modal should be associated with when opening or closing.

    // src/pages/+login.tsx
    
    import { Modal } from '@/ui'
    
    export default function Login() {
      return <Modal>Content</Modal>
    }
    
    // src/pages/_app.tsx
    
    import { Outlet } from 'react-router'
    import { useModals } from '../router'
    
    export default function App() {
      const modals = useModals()
    
      return (
        <section>
          <header>
            <nav>...</nav>
            <button onClick={() => modals.open('/login')}>Open modal</button>
          </header>
    
          <main>
            <Outlet />
          </main>
        </section>
      )
    }
  6. Implement type-safe global modals

    main

    You can create global modal routes by prefixing a file name in src/pages/ with a plus sign +. For example, src/pages/+login.tsx defines a modal route.

    To control modals, use the useModals hook imported from src/router.ts. This hook provides open and close methods that are type-safe and support an at option to specify which route the modal should be active on.

    • modals.open(path, options)
    • modals.close(options)

    The options object includes an at property, which accepts a valid route path.

    // src/pages/+login.tsx
    
    import { Modal } from '@/ui'
    
    export default function Login() {
      return <Modal>Content</Modal>
    }
    
    // src/pages/_app.tsx
    
    import { ParentProps } from 'solid-js'
    import { useModals } from '../router'
    
    export default function App(props: ParentProps) {
      const modals = useModals()
    
      return (
        <section>
          <header>
            <nav>...</nav>
            <button onClick={() => modals.open('/login')}>Open modal</button>
          </header>
    
          <main>{props.children}</main>
        </section>
      )
    }
    
    // Example usage of 'at' option:
    // modals.open('/login', { at: '/auth', replace: true })
    // modals.open('/info', { at: '/invoice/:id', params: { id: 'xyz' } })
    // modals.close({ at: '/', replace: false })
  7. File and directory naming conventions for routes

    main

    Generouted uses a file-system based routing convention located in src/pages. It supports .tsx, .jsx, and .mdx extensions.

    Route Types

    • Index routes: src/pages/index.tsx maps to /. src/pages/posts/index.tsx maps to /posts.
    • Nested routes: src/pages/posts/2022/index.tsx maps to /posts/2022.
    • Dynamic routes: src/pages/posts/[slug].tsx maps to /posts/:slug.
    • Catch-all routes: src/pages/posts/[...all].tsx maps to /posts/*.
    • Pathless layouts: Wrap a directory in parentheses () to create a layout that doesn't affect the URL path. Example: src/pages/(auth)/login.tsx maps to /login.
    • Global modals: Prefix a filename with + to create a modal route. Example: src/pages/+info.tsx maps to /info. Use the useModals() hook to navigate.
    • Optional segments: Prefix a segment with - to make it optional. Example: src/pages/-[lang]/about.tsx maps to /:lang?/about (matches /en/about or /about).
    • Ignored routes: Any file or directory starting with an underscore _ is ignored by the router. Example: src/pages/_components/.
    • Nested URLs without layouts: Use dots . between segments to create nested URLs without a corresponding layout directory. Example: src/pages/posts.nested.tsx maps to /posts/nested.
  8. Implement protected or guarded routes

    main

    To implement protected routes, create a redirection component that checks authentication state and wrap your root-level layout (src/pages/_app.tsx) with it.

    Example Implementation

    1. Define the redirection logic:
    // 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
    }
    1. Wrap the <Outlet /> in src/pages/_app.tsx:
    // src/pages/_app.tsx
    import { Outlet } from 'react-router'
    import { Redirects } from '../config/redirects'
    
    export default function App() {
      return (
        <section>
          <header>...</header>
          <main>
            <Redirects>
              <Outlet />
            </Redirects>
          </main>
        </section>
      )
    }
  9. Configure MDX support in Vite for Generouted

    main

    To use .mdx files as pages within your src/pages directory, you must install @mdx-js/rollup and configure it in your vite.config.ts. It is critical to use the enforce: 'pre' option in the plugin configuration to ensure MDX files are processed before other plugins.

    // vite.config.ts
    
    import { defineConfig } from 'vite'
    import react from '@vitejs/plugin-react'
    import generouted from '@generouted/react-router/plugin'
    import mdx from '@mdx-js/rollup'
    
    export default defineConfig({ plugins: [{ enforce: 'pre', ...mdx() }, react(), generouted()] })
  10. Setup Generouted with Solid Router

    main

    To use file-based routing with Solid Router in a Vite project, install the required packages, add the Generouted plugin to your Vite configuration, and use the <Routes /> component in your application entry point. Pages are defined by creating files in the src/pages directory and exporting a default component.

    pnpm add @generouted/solid-router @solidjs/router
    // 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()] })
    // src/main.tsx
    import { render } from 'solid-js/web'
    import { Routes } from '@generouted/solid-router'
    
    render(Routes, document.getElementById('root')!)
    // src/pages/index.tsx
    export default function Home() {
      return <h1>Home</h1>
    }