Skip to content

Instantly share code, notes, and snippets.

@tomfa
Last active November 15, 2024 05:51
Show Gist options
  • 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;
};
@driftee
Copy link

driftee commented Aug 2, 2024

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

You messed up the param order. matchesPathname('/cake', '/[id]') actually works.

@mbunge
Copy link

mbunge commented Oct 15, 2024

Great Job!

I've simplyfied the if statement, since test always returns a boolean:

before:

  if (basePathRegex.test(baseAsPath)) {
    return true;
  }
  return false;

after:

   return basePathRegex.test(baseAsPath);

full implementation:

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, ".*"),
  );
  return basePathRegex.test(baseAsPath);
};

Test results

Screenshot 2024-10-15 115128

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