1.1.1 • Published 3 months ago

@meta-ultra/app-router v1.1.1

Weekly downloads
-
License
MIT
Repository
-
Last release
3 months ago

Welcome to @meta-ultra/app-router

With @meta-ultra/app-router, it's not only possible to structure the application routing in a meaningful, intuitive and highly-maintainable way as the Next.js App Router does, but also allow for improving user experience effortlessly.

If you're familiar to Next.js 13 or later version, or you've got stuck in providing great UX with react-router, @meta-ultra/app-router will be the place where you're looking forward.

Give a ⭐️ if this project helped you!

🌟 Features

  • Follows the file conventions and functionalities of Next.js App Router, such as page, layout, template,loading, not-found, error and route.
  • Supports receiving params and searchParams as props in page, layout.
  • Supports dynamic routes including catch-all segments and optional catch-all segments.(by setting route id)
  • Supports default root layout.
  • Supports advanced feature - parallel routes with its own template, loading, not-found and error.
  • Supports advanced feature - fully-functional intercepting routes start with (.), (..), (..)(..) and (...).
  • Besides the context free notFound as Next.js does, an extra notFound with context created by useNotFound() is available.
  • It's capable of setting the document.title, and other metadata like description, keywords and author through metadata or generateMetadata APIs as Next.js does on Server Components.(At this moment, only basic fields are supported)
  • Supports route handlers including dynamic route feature.
  • Based on React Router v6, it's free to structure the application routing and project file-system hierarchy, although it's recommended to abide by the rules of Next.js App Router.
  • @meta-ultra/app-router is written with type safety in mind through TypeScript.

🏠 Installation

Install @meta-ultra/app-router with your favorite package manager:

  • pnpm: pnpm add @meta-ultra/app-router@latest
  • yarn: yarn add @meta-ultra/app-router@latest
  • npm: npm install -S @meta-ultra/app-router@latest

✨ Usage

Let's demonstrate how to use @meta-ultra/app-router with Next.js App Router conventions.

  • Project structure:

    /src
     - router.tsx
     - /app
      - global-error.tsx
      - layout.tsx
      - loading.tsx
      - not-found.tsx
      - page.tsx
      - (system)
        - layout.tsx
        - /users
          - page.tsx
        - /posts
          - error.tsx
          - loading.tsx
          - page.tsx
  • Defines routes object with RouteSegmentElement:

    // ./src/router.tsx
    import { lazy } from "react";
    import { createHashRouter } from "react-router-dom";
    import {
      RootLayoutRouteElement,
      RootErrorElement,
      LayoutRouteElement,
      PageRouteElement,
    } from "@meta-ultra/app-router";
    import RootLoading from "./app/loading"
    import RootNotFound from "./app/not-found"
    import GLobalError from "./app/global-error"
    import PostsLoading from "./app/(system)/posts/loading"
    import PostsError from "./app/(system)/posts/error"
    
    const router = createHashRouter([
      {
        path: "/",
        element: (
          <RootLayoutRouteElement 
            loading={RootLoading} 
            error={GlobalError}
            notFound={RootNotFound}
          >
            {lazy(() => import("./app/layout"))}
          </RootLayoutRouteElement>
        ),
        errorElement: (<RootErrorElement notFound={RootNotFound} />)
        children: [
          {
            index: true,
            element: (<PageRouteElement>{lazy(() => import("./app/page"))}</PageRouteElement>),
          },
          {
            element: (
              <LayoutRouteElement>
                {lazy(() => import("./app/(system)/layout"))}
              </LayoutRouteElement>
            ),
            children: [
              {
                path: "users",
                element: (<PageRouteElement>{lazy(() => import("./app/(system)/users/page"))}</PageRouteElement>),
              },
              {
                path: "posts"
                element: (<PageRouteElement loading={PostsLoading} error={PostsError}>{lazy(() => import("./app/(system)/posts/page"))}</PageRouteElement>),
              }
            ]
          }
        ]
      }
    ])
    export default router
  • Defines the root layout, loading, not-found and global-error.

    // ./src/app/layout.tsx
    import { type FC } from "react"
    import { type GenerateMetadata, useNotFound } from "@meta-ultra/app-router"
    import { useLocation } from "react-router-dom"
    
    // Naming starts with `use` to pass the validations of using React hooks.
    export const generateMetadata = async function useGenerateMetadata(
      { params, searchParams }, 
      parentMetadataPromise
    ) {
      const location = useLocation()
      const notFound = useNotFound()
    
      // Notice that, the second parameter is an instance of Promise here, 
      // rather not Metadata plain old object in Next.js.
      const parentMetadata = await parentMetadataPromise
    
      // It's able to do some authorization works, except changing application metadata.
      const perms = await getPermissions()
      const perm = permissions.find(perm => perm.pathname === location.pathname)
      if (!perm) {
        return notFound()
      }
    
      return {
        title: perm.title,
        description: perm.description,
        keys: parentMetadata.keys,
      }
    } 
    
    const RootLayout: FC<PropsWithChildren> = ({children}) => {
      return children
    }
    export default RootLayout
    
    // ./src/loading.tsx
    export default function RootLoading() {
      return <div>Loading...</div>
    }
    
    // ./src/not-found.tsx
    import { type FC } from "react"
    import { type ErrorResponse } from "@meta-ultra/app-router"
    import { useNavigate } from "react-router-dom"
    
    const RootNotFound: FC<{error: ErrorResponse}> = ({error}) => {
      const navigate = useNavigate()
      const onReset = () => {
        /**
         * NOTE:
         * Due to the client router has been broken when not found occurs,
         * change the client router to home page first, and then refresh browser 
         * to reinitialize the whole client router from scratch.
         */ 
        navigate("/")
        location.reload()
      }
    
      return (
        <main>
          <p>{error.message}</p>
          <button onClick={onReset}>Go to home page!</button>
        </main>
      )
    }
    export default RootNotFound
    
    // ./src/global-error.tsx
    import { type FC } from "react"
    import { type ErrorProps } from "@meta-ultra/app-router"
    
    const GlobalError: FC<ErrorProps> = ({error, reset}) => {
      return (
        <main>
          <p>{error.message}</p>
          <button onClick={() => reset()}>Click to reset!</button>
        </main>
      )
    }
    export default GlobalError

👶 Author

Hey, friends. I'm John Huang, a full stack developer majorly code with React, Next.js, GraphQL, TailwindCSS, Taro and SpringBoot. Feel free to contact with me 😃

🤝 Contributing

Contributions, issues and feature requests are welcome! Feel free to check issues page.

1.1.1

3 months ago

1.1.0

4 months ago

1.0.0

4 months ago

0.6.1

5 months ago

0.6.0

5 months ago

0.5.0

5 months ago

0.4.0

5 months ago

0.3.5

6 months ago

0.3.4

6 months ago

0.3.3

6 months ago

0.3.2

6 months ago

0.3.1

6 months ago

0.3.0

6 months ago

0.2.2

6 months ago

0.2.1

6 months ago

0.1.0

7 months ago