Skip to content

Instantly share code, notes, and snippets.

@tomfa
Last active March 6, 2024 17:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomfa/f925366cd036bb0d4af5bbd8397c84ae to your computer and use it in GitHub Desktop.
Save tomfa/f925366cd036bb0d4af5bbd8397c84ae to your computer and use it in GitHub Desktop.
NextJS route vs pathname matcher
describe('matchesPath', () => {
const matches = [
['/cake', '/cake'],
['/cake', '/cake/'],
['/cake', '/cake?frige=warm'],
['/cake', '/cake?frige=warm&freezer=cold'],
['/[id]', '/cake'],
['/[anything-goes]', '/cake'],
['/c/[id]/practitioner/[pid]/[anything-goes]', '/c/1/practitioner/2/3'],
['/[...rest]', '/cake'],
['/[...rest]', '/cake/fake/snake?shake=true'],
['/shop/[[...rest]]', '/shop'],
['/shop/[[...rest]]', '/shop/'],
['/shop/[[...rest]]', '/shop/snake'],
['/[...rest]/fake/snake', '/cake/fake/snake?shake=true'],
[
'/welcome',
'/welcome/?verifier=z3vvsSm',
],
] as Array<[string, string]>;
const nonMatches = [
['/stake', '/snake'],
['/cake', '/cake/subpath-not-ok'],
['/cake/[oh-whats-this]', '/cake/'],
['/[...rest]/nope/snake', '/cake/fake/snake?shake=true'],
['/[...rest]', '/'],
] as Array<[string, string]>;
matches.forEach(([path, asPath]) => {
it(`matches ${asPath} to ${path}`, () => {
expect(matchesPathname(asPath, path)).toEqual(true);
});
});
nonMatches.forEach(([path, asPath]) => {
it(`does not match ${asPath} to ${path}`, () => {
expect(matchesPathname(asPath, path)).toEqual(false);
});
});
});
export const removeTrailingSlash = (val: string) =>
val.endsWith('/') ? val.substring(0, val.length - 1) : val;
export const matchesPathname = (asPath: string, pathname: string) => {
if (asPath === pathname) {
return true;
}
const baseAsPath = removeTrailingSlash(asPath.split('?')[0] as string);
const basePathname = removeTrailingSlash(pathname.split('?')[0] as string);
if (baseAsPath === basePathname) {
return true;
}
const basePathRegex = new RegExp(
`^${basePathname.replace(/(\[[a-zA-Z0-9-]+\])+/g, '[a-zA-Z0-9-]+')}$`
.replace(/\[\[\.\.\.[a-zA-Z0-9-]+\]\]/g, '?.*')
.replace(/\[\.\.\.[a-zA-Z0-9-]+\]/g, '.*'),
);
if (basePathRegex.test(baseAsPath)) {
return true;
}
return false;
};
@marcelkooi
Copy link

Thanks for providing this!

You'll need to define a removeTrailingSlash function such as the following:

function removeTrailingSlashes(pathname: string) {
  if (pathname.endsWith("/")) {
    return pathname.slice(0, -1);
  }
  return pathname;
}

I needed the regex to match = characters as well, so I changed it to [a-zA-Z0-9-=]+

@jsBlaster
Copy link

jsBlaster commented Nov 6, 2023

Hi @tomfa thank you for this function.
Just a small tip, in order to handle also the Optional Catch-all Segments

image

we need to change the regex into

const basePathRegex = new RegExp(
    `^${basePathname.replace(
      /(\[[a-zA-Z0-9-]+\])+/g,
      '[a-zA-Z0-9-]+'
    )}$`.replace(/\[+\.\.\.[a-zA-Z0-9-]+\]+/g, '?.*')

Because we need to

  • check if we have [[ instead of [ for the initial checking
  • make the last / optional because with the Optional Optional Catch-all Segments, as the doc said, the route without the parameter is also matched

@tomfa
Copy link
Author

tomfa commented Nov 6, 2023

I've updated the gist to take Optional Catch-all Segments + added the missing function. Thanks for the help! ❤️

@joe-akers-douglas-otm
Copy link

This no longer seems to work?

@joe-akers-douglas-otm
Copy link

The paths with dynamic segments (ie matchesPathname('/[id]', '/cake')) return false

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment