Skip to content

Instantly share code, notes, and snippets.

@tricoder42 tricoder42/README.md
Last active May 14, 2019

Embed
What would you like to do?
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
You can’t perform that action at this time.