Next.js: The basePath is repeated twice in URL with query params

Created on 15 Sep 2020  路  7Comments  路  Source: vercel/next.js

Bug report

Describe the bug

The basePath is repeated twice in URL when we add query parameters. Say we have set basePath: '/base' in next.config.js, if we enter the /base?foo=bar, it changes to the /base/base?foo=bar.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Run the https://repl.it/@theshem/NextBasePathWithQueryParams
  2. Navigate to https://nextbasepathwithqueryparams.theshem.repl.co/base?foo=bar

Expected behavior

The URL should remain unchanged. I.e. /base?foo=bar.

Screenshots

System information

  • OS: macOS 10.14.5
  • Browser (if applies) Chrome 85
  • Version of Next.js: 9.5.3
  • Version of Node.js: 12.x
bug

Most helpful comment

Well, since the PR with a fix didn't get any attention so far, I have implemented an ugly workaround. Just call this hook in you index page and it should get the work done; I lose hash part of URL if also query string is present, but other than that it seems to get the job done:

import {useRouter} from 'next/router'

export function useBasepathFix(): void {
  const {basePath} = useRouter()
  if (typeof window === 'undefined') {
    return
  }
  if (window.location.pathname === basePath + basePath) {
    const fixedUrl = basePath + window.location.search + window.location.hash
    const newState = {
      ...window.history.state,
      as: fixedUrl,
    }
    window.history.replaceState(newState, undefined, fixedUrl)
  }
}

(Edit: Removed interpolation, replicates stored history state)

All 7 comments

What should happen if you also have a page ./pages/base.js or ./page/[dynamic].js? Should it link to that page? Or still to the root? Should the Link behave differently depending on whether you have a page like that or not?

Personally, I don't think Link should magically strip the basepath. It creates too many ambiguities. One potential solution next.js could implement is a basePath property, similar to the one that is accepted by custom redirects.

<Link basePath={false} href='/base'>

In the meantime you could always write a Link wrapper that does this for you. Something along the lines of:

import Link from 'next/link'
import { useRouter } from 'next/router'

export function RootRelativeLink ({ href, ...props }) {
  const { basePath } = useRouter()
  const newHref = href.slice(basePath.length)
  return <Link href={newHref.startsWith('/') ? newHref : `/${newHref}`} {...props} />
}

@Janpot Thank you for the response. But I'm afraid that is not the case. It is not about the Link component. The example above does not use it.

The issue is:
If a basePath is set in next.config.js file, by adding query params to the URL the basePath is repeated unnecessarily. No router thing involved explicitly.

Am I missing something here?

Ah, I didn't read the issue well enough it seems. Nope, this is a bug.

+1 for this bug. Perhaps an interesting observation here is that it seems to affect only homepage. Building upon the original repl.it, I have added a subpage and the path duplication doesn't happen: https://nextbasepathwithqueryparams.janvlnas.repl.co/base/sub/file?some=query

I suspect this will be an issue with basePath matching somewhere in next-server/lib/router/router.ts; I will give it some time to debug it today.

I have traced the issue down to prepareUrlAs function when called via router.replace in componentDidMount.

When accessing the homepage at /some/base/path?some=query, as already contains the basePath while url does not:

image

But if I call the function on subpath, as does not contain the basePath (in this case I am accessing /some/base/path/another/page?some=query):

image

So I think this seems like a special treatment of / url in as. Let's see if I can do something about it.

So the root cause is in the hasBasePath function which fails to match root URL with query string:

https://github.com/vercel/next.js/blob/67b67b28b229fca0bd937d154270c6a6312e2bce/packages/next/next-server/lib/router/router.ts#L50-L52

This works for subpath when path = '/some/base/path/another/page?some=query' and basePath = '/some/base/path' since it will fulfill the startsWith condition.
But if path = '/some/base/path?some=query' it satisfies neither. I think the safest fix here would be to explicitly remove query string from path inside that function but perhaps some maintainer can guide me to a cleaner solution.

Well, since the PR with a fix didn't get any attention so far, I have implemented an ugly workaround. Just call this hook in you index page and it should get the work done; I lose hash part of URL if also query string is present, but other than that it seems to get the job done:

import {useRouter} from 'next/router'

export function useBasepathFix(): void {
  const {basePath} = useRouter()
  if (typeof window === 'undefined') {
    return
  }
  if (window.location.pathname === basePath + basePath) {
    const fixedUrl = basePath + window.location.search + window.location.hash
    const newState = {
      ...window.history.state,
      as: fixedUrl,
    }
    window.history.replaceState(newState, undefined, fixedUrl)
  }
}

(Edit: Removed interpolation, replicates stored history state)

Was this page helpful?
0 / 5 - 0 ratings