Skip to content

Instantly share code, notes, and snippets.

@tricoder42
Last active February 10, 2023 08:54
Show Gist options
  • Save tricoder42/bbfcc9a946db105cf2d13b51d78b268c to your computer and use it in GitHub Desktop.
Save tricoder42/bbfcc9a946db105cf2d13b51d78b268c to your computer and use it in GitHub Desktop.
Centralized routing for Next.js

I'm using this approach to have centralized route config in Next.js. I don't have the fully automated solution. Right now I'm writing route configs and links manually to figure out if it works and what are the drawbacks.

I believe the automation is the last step to make it completely seamless.

1. Define routes in centralized config

This is the only file that needs to be wrote manually.

//route.config.ts

const routeConfig = {
  // url: filename inside `pages`
  '/about': '/about',
  '/article/:id': '/article',
}

2. Links and route configs are generated

Only imported Links/routes are included in bundle, everything else is stripped down using tree-shaking.

Each route is transformed into routeConfig and custom Link component. routeConfig can be used for imperative routing (e.g. Router.push), while custom Link is used in JSX for declarative routing.

// links.ts
// generated from route.config.ts

export const about = makeRouteConfig("/about")
export const AboutLink = makeLink(about)

export const article = makeRouteConfig("/article/:id", "/article")
export const ArticleLink = makeLink(article)

As a bonus, links can be typechecked:

// links.d.ts
interface CustomLinkProps<Params> extends LinkProps {
  params: Params
}

interface ArticleLinkParams {}
declare const ArticleLink = React.ElementType<CustomLinkProps<AboutLinkParams>>

3. now.json is generated as well

{
  "routes": [
    { "src": "/about", "dest": "/about.js" },
    { "src": "/article/(?<id>[^/]*)", "dest": "/article.js?id=$id" },
  ]
}

4. Example

// pages/index.ts

import { AboutLink, ArticleLink } from "../links"

export default () => {
  <nav>
    <AboutLink>About</AboutLink>
    <ArticleLink params={{id: "42"}}>Answering the ultimate question</ArticleLink>
  </nav>
}

Summary

✅ this can be build on top of current Next.js API

✅ centralized route config with minimal footprint in bundle

✅ global server handler is used only for development. Pages in production can be served in serverless fashion.

🚫 requires extra compile step to prepare now.json and links.ts

Implementation details

Current implementation of helpers which I use in previous examples. Not important for the concept.

makeRouteConfig

Little helper to create routeConfig object.

export interface RouteConfig {
  path: string
  as: string
}

export function makeRouteConfig(as: string, path?: string): RouteConfig {
  if (process.env.NODE_ENV !== "production") {
    if (!as.startsWith("/")) {
      throw Error(`path ${as} should start with /`)
    }
  }

  return {
    as,
    path: path != null ? path : as
  }
}

makeLink

Factory to make custom Link with href and as attributes filled automatically based on routeConfig object

import * as React from "react"
import Link, { LinkProps } from "next/link"
import { reverse, RouteConfig } from "accent/core/routes"

export interface RouteLinkProps extends LinkProps {
  params?: { [key: string]: string }
}

// Create customized Link component, which fills `href` and `as` props based on routeConfig
export const makeLink = (routeConfig: RouteConfig) => ({
  params,
  children,
  ...linkProps
}: RouteLinkProps) => {
  const href = {
    pathname: routeConfig.path,
    query: params
  }

  const as = params == null ? routeConfig.as : reverse(routeConfig, params)

  return (
    <Link href={href} as={as} {...linkProps}>
      {children}
    </Link>
  )
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment