Skip to content

Commit

Permalink
Append the fragment in NextUrl.toString() (vercel#41501)
Browse files Browse the repository at this point in the history
<!--
Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change that you're making:
-->

Consider the following middleware which redirects to
`/path/to#fragment`.

```ts
import { NextResponse } from 'next/server';

export async function middleware(request) {
  const url = new URL('/path/to#fragment', request.url);
  return NextResponse.redirect(url);
}
```

However, it actually redirects to `/path/to`, namely it discards the
fragment part in the destination URL `#fragment`. This is because
`NextURL.toString()` does not append that part. This PR fixes the bug.

## Bug

- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have a helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have a helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing
doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
  • Loading branch information
nuta authored and Kikobeats committed Oct 24, 2022
1 parent 646f01a commit 94206f0
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/next/server/web/next-url.ts
Expand Up @@ -181,7 +181,7 @@ export class NextURL {
get href() {
const pathname = this.formatPathname()
const search = this.formatSearch()
return `${this.protocol}//${this.host}${pathname}${search}`
return `${this.protocol}//${this.host}${pathname}${search}${this.hash}`
}

set href(url: string) {
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/middleware-redirects/app/middleware.js
Expand Up @@ -75,4 +75,9 @@ export async function middleware(request) {
url.searchParams.delete('pathname')
return Response.redirect(url)
}

if (url.pathname === '/with-fragment') {
console.log(String(new URL('/new-home#fragment', url)))
return Response.redirect(new URL('/new-home#fragment', url))
}
}
14 changes: 14 additions & 0 deletions test/e2e/middleware-redirects/test/index.test.ts
Expand Up @@ -157,6 +157,20 @@ describe('Middleware Redirect', () => {
.join('\n')
expect(errors).not.toContain('Failed to lookup route')
})

// A regression test for https://github.com/vercel/next.js/pull/41501
it(`${label}should redirect with a fragment`, async () => {
const res = await fetchViaHTTP(next.url, `${locale}/with-fragment`)
const html = await res.text()
const $ = cheerio.load(html)
const browser = await webdriver(next.url, `${locale}/with-fragment`)
try {
expect(await browser.eval(`window.location.hash`)).toBe(`#fragment`)
} finally {
await browser.close()
}
expect($('.title').text()).toBe('Welcome to a new page')
})
}
tests()
testsWithLocale()
Expand Down
9 changes: 9 additions & 0 deletions test/unit/web-runtime/next-url.test.ts
Expand Up @@ -39,6 +39,15 @@ it('does noop changing to an invalid hostname', () => {
expect(url.toString()).toEqual('https://foo.com/example')
})

it('preserves the fragment', () => {
const url = new NextURL(
'https://example.com/path/to?param1=value1#this-is-fragment'
)
expect(url.toString()).toEqual(
'https://example.com/path/to?param1=value1#this-is-fragment'
)
})

it('allows to change the whole href', () => {
const url = new NextURL('https://localhost.com/foo')
expect(url.hostname).toEqual('localhost.com')
Expand Down

0 comments on commit 94206f0

Please sign in to comment.