Skip to content

Instantly share code, notes, and snippets.

@aweber1
Last active May 26, 2023 13:26
Show Gist options
  • Select an option

  • Save aweber1/85e0ce9ca6df74b4374d27cd0523c78a to your computer and use it in GitHub Desktop.

Select an option

Save aweber1/85e0ce9ca6df74b4374d27cd0523c78a to your computer and use it in GitHub Desktop.

Revisions

  1. Adam Weber revised this gist Mar 29, 2020. 2 changed files with 12 additions and 4 deletions.
    11 changes: 8 additions & 3 deletions Link-usage.js
    Original file line number Diff line number Diff line change
    @@ -3,8 +3,13 @@ import Link from './Link';
    export function MyComponent(props) {
    // NOTE: you can use all of the same props used for `next/link`
    return (
    <Link href="/some-rad-page">
    <a>My Link Text</a>
    </Link>
    <div>
    <Link href="/some-rad-page">
    <a>My Link Text</a>
    </Link>
    <Link href="/en/some-rad-page">
    <a>My Other Link Text</a>
    </Link>
    </div>
    );
    }
    5 changes: 4 additions & 1 deletion Page-usage.js
    Original file line number Diff line number Diff line change
    @@ -8,6 +8,9 @@ MyPage.getInitialProps = (nextContext) => {
    // `pathname` should be `index`, i.e. the `destination` property in our route matcher definitions.
    console.log('pathname', pathname);
    // `query.routeName` should be `/some-rad-page`, i.e. the `:routeName` parameter matched in our route matcher definitions.
    console.log('query.routeName', query.routeName);
    console.log('query.routeName', query.routeName);
    // `query.lang` may not be defined as it is "optional" in our route matcher definitions.
    // However, if a route url is, for example, `/en/some-rad-page`, then `query.lang` would be `en`.
    console.log('query.lang', query.lang);

    }
  2. Adam Weber created this gist Mar 29, 2020.
    10 changes: 10 additions & 0 deletions Link-usage.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,10 @@
    import Link from './Link';

    export function MyComponent(props) {
    // NOTE: you can use all of the same props used for `next/link`
    return (
    <Link href="/some-rad-page">
    <a>My Link Text</a>
    </Link>
    );
    }
    26 changes: 26 additions & 0 deletions Link.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,26 @@
    import Link from 'next/link';
    import { matchRoute } from './routeMatcher';

    // This component acts as an abstraction around the `next/link` component to:
    // 1. Provide the ability to use regex patterns when declaring dynamic routes.
    // 2. Help reduce refactoring if routing libraries change.

    // This component is intended to accept the same props interface as the `next/link` component.
    // Next.js has the concept of dynamic routes / custom routes, but they are not suited for regex
    // matching on both server _and_ client.

    export default ({ href, ...otherProps }) => {
    if (href && typeof href === 'string') {
    const { matchedRoute, matchedDefinition } = matchRoute(href);
    if (matchedRoute && matchedDefinition) {
    return (
    <Link
    {...otherProps}
    href={{ pathname: matchedDefinition.destination, query: matchedRoute.params }}
    as={matchedRoute.path}
    />
    );
    }
    }
    return <Link href={href} {...otherProps} />;
    };
    13 changes: 13 additions & 0 deletions Page-usage.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,13 @@
    const MyPage = () => {
    return <div></div>;
    };

    MyPage.getInitialProps = (nextContext) => {
    const { pathname, query } = nextContext;

    // `pathname` should be `index`, i.e. the `destination` property in our route matcher definitions.
    console.log('pathname', pathname);
    // `query.routeName` should be `/some-rad-page`, i.e. the `:routeName` parameter matched in our route matcher definitions.
    console.log('query.routeName', query.routeName);

    }
    9 changes: 9 additions & 0 deletions next.config.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,9 @@
    const { rewrites } = require('./routeMatcher');

    const nextConfig = {
    experimental: {
    rewrites: () => rewrites,
    },
    };

    module.exports = nextConfig;
    5 changes: 5 additions & 0 deletions package.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    {
    "dependencies": {
    "path-to-regexp": "^6.1.0"
    }
    }
    53 changes: 53 additions & 0 deletions routeMatcher.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,53 @@
    // This file uses CommonJS because it is used by both server and client.
    const { match: createMatcher } = require('path-to-regexp');

    // `rewrites` are using by `next.config.experimental.rewrites` property and must be an
    // array of objects with _only_ `source` and `destination` properties.
    const rewrites = [
    {
    source: '/:lang([a-z]{2}-[A-Z]{2})/:routeName*',
    destination: '/index',
    },
    {
    source: '/:lang([a-z]{2})/:routeName*',
    destination: '/index',
    },
    {
    source: '/:routeName*',
    destination: '/index',
    },
    ];

    // We create a new set of definitions mapped from `rewrites` and attach
    // a `matcher` property that we can use for easier match evaluation in
    // consuming code.
    const routeMatcherDefinitions = rewrites.map((rewrite) => {
    return { ...rewrite, matcher: createMatcher(rewrite.source) };
    });

    // matchRoute iterates `routeMatcherDefinitions` and attempts to match
    // and parse the given `path`.
    function matchRoute(path) {
    let matchedRoute = null;
    let matchedDefinition = null;

    // Using a `for` loop allows us to break on the first match.
    // Why not `for ... of`? because for...of loops get transpiled to something big and clunky.
    // Why not Array.forEach or Array.reduce? because we can't break early.
    for (let i = 0; i < routeMatcherDefinitions.length; i++) {
    const routeDefinition = routeMatcherDefinitions[i];
    // matcher returns false if no match made
    matchedRoute = routeDefinition.matcher(path);
    if (matchedRoute) {
    matchedDefinition = routeDefinition;
    break;
    }
    }
    return { matchedRoute, matchedDefinition };
    }

    module.exports = {
    matchRoute,
    rewrites,
    routeMatcherDefinitions,
    };