Skip to content

Support relative links #511

Description

@maxsalven

Would love to see support for relative links in Wouter!

URL: https://www.example.com/some/long/unnested/route/foo

<Link to="../bar">Takes you to example.com/some/long/unnested/route/bar</Link>

I appreciate we can nest things but that's not always an appropriate option.

Activity

  1. molefrog commented on Feb 21, 2025

    @molefrog
    Owner

    It's not something we have in the roadmap, but maybe this can be solved with a custom wrapper around Link component e.g.

    // under the hood, takes current location from `useLocation`, resolves the absolute path and renders a regular Link
    <RelLink to="../bar" />

    If you get it working, please let me know, maybe we can add this to the README.

  2. richardgarnier commented on Sep 15, 2025

    @richardgarnier

    I am not sure I understood the assignment correctly, but I have a similar issue.
    For me thinking in terms of "unix" paths is easier when building my routes.

    For now, I've settled with this solution:

    import { useCallback } from 'react';
    import { useLocation, useRouter } from 'wouter';
    
    type Return = ReturnType<typeof useLocation>;
    
    function join(...paths: string[]): string {
      const parts = paths.flatMap(p => p.split('/'));
      const newParts: string[] = [];
    
      for (const part of parts) {
        if (part === '' || part === '.') {
          continue;
        }
        if (part === '..') {
          newParts.pop();
        } else {
          newParts.push(part);
        }
      }
    
      if (newParts.length === 0) {
        return '/';
      }
    
      return '/' + newParts.join('/');
    }
    
    export function useRelativeLocation(base = '/'): Return {
      const { hook } = useRouter();
      const [location, navigate] = hook(); // `useBrowserLocation` by default
    
      const relativeNavigate = useCallback(
        (to: string, options: Parameters<Return[1]>[1]) => {
          if (to.startsWith('.')) {
            const newPath = join(location, to);
    
            if (base === '/' || newPath === base || newPath.startsWith(`${base}/`)) {
              return navigate(newPath, options);
            }
          } else if (to.startsWith('/')) {
            if (base === '/' || to === base || to.startsWith(`${base}/`)) {
              return navigate(to, options);
            }
          }
    
          return navigate(base);
        },
        [base, location, navigate],
      );
    
      return [location, relativeNavigate];
    }

    Note that the base param is passed to useRelativeLocation because the base returned in useRouter is messed up by nesting contexts. Also when trying to navigate outside the basePath, instead of adding it automatically, it defaults on going back to the base path, which may or may not be what you wanted.

    Implementing matching and should be easy, so I didn't include them here.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions