Skip to content

Commit

Permalink
Add basePath in link component and add/remove it consistently
Browse files Browse the repository at this point in the history
  • Loading branch information
ijjk committed Jan 8, 2020
1 parent 3c8fff3 commit 32c26be
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 18 deletions.
5 changes: 3 additions & 2 deletions packages/next/client/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
formatWithValidation,
getLocationOrigin,
} from '../next-server/lib/utils'
import { addBasePath } from '../next-server/lib/router/router'

function isLocal(href: string) {
const url = parse(href, false, true)
Expand Down Expand Up @@ -150,8 +151,8 @@ class Link extends Component<LinkProps> {
// as per https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
formatUrls = memoizedFormatUrl((href, asHref) => {
return {
href: formatUrl(href),
as: asHref ? formatUrl(asHref) : asHref,
href: addBasePath(formatUrl(href)),
as: asHref ? addBasePath(formatUrl(asHref)) : asHref,
}
})

Expand Down
26 changes: 17 additions & 9 deletions packages/next/next-server/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ import { isDynamicRoute } from './utils/is-dynamic'
import { getRouteMatcher } from './utils/route-matcher'
import { getRouteRegex } from './utils/route-regex'

function addBasePath(path: string): string {
// @ts-ignore variable is always a string
const p: string = process.env.__NEXT_ROUTER_BASEPATH
return path.indexOf(p) !== 0 ? p + path : path
// @ts-ignore variable is always a string
const basePath: string = process.env.__NEXT_ROUTER_BASEPATH

export function addBasePath(path: string): string {
return path.indexOf(basePath) !== 0 ? basePath + path : path
}

function delBasePath(path: string): string {
return path.replace(new RegExp(`^${basePath}`), '')
}

function toRoute(path: string): string {
Expand Down Expand Up @@ -274,9 +279,12 @@ export default class Router implements BaseRouter {

// If url and as provided as an object representation,
// we'll format them into the string version here.
const url = typeof _url === 'object' ? formatWithValidation(_url) : _url
let url = typeof _url === 'object' ? formatWithValidation(_url) : _url
let as = typeof _as === 'object' ? formatWithValidation(_as) : _as

url = addBasePath(url)
as = addBasePath(as)

// Add the ending slash to the paths. So, we can serve the
// "<page>/index.html" directly for the SSR page.
if (process.env.__NEXT_EXPORT_TRAILING_SLASH) {
Expand All @@ -299,7 +307,7 @@ export default class Router implements BaseRouter {
if (!options._h && this.onlyAHashChange(as)) {
this.asPath = as
Router.events.emit('hashChangeStart', as)
this.changeState(method, url, addBasePath(as))
this.changeState(method, url, as)
this.scrollToHash(as)
Router.events.emit('hashChangeComplete', as)
return resolve(true)
Expand Down Expand Up @@ -361,7 +369,7 @@ export default class Router implements BaseRouter {
}

Router.events.emit('beforeHistoryChange', as)
this.changeState(method, url, addBasePath(as), options)
this.changeState(method, url, as, options)
const hash = window.location.hash.substring(1)

if (process.env.NODE_ENV !== 'production') {
Expand Down Expand Up @@ -614,8 +622,7 @@ export default class Router implements BaseRouter {
return
}

// @ts-ignore pathname is always defined
const route = toRoute(pathname)
const route = delBasePath(toRoute(pathname))
this.pageLoader.prefetch(route).then(resolve, reject)
})
}
Expand All @@ -625,6 +632,7 @@ export default class Router implements BaseRouter {
const cancel = (this.clc = () => {
cancelled = true
})
route = delBasePath(route)

const Component = await this.pageLoader.loadPage(route)

Expand Down
15 changes: 8 additions & 7 deletions packages/next/next-server/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,15 @@ export default class Server {
parsedUrl.query = parseQs(parsedUrl.query)
}

if (parsedUrl.pathname!.startsWith(this.nextConfig.experimental.basePath)) {
const { basePath } = this.nextConfig.experimental

// if basePath is set require it be present
if (basePath && !req.url!.startsWith(basePath)) {
return this.render404(req, res, parsedUrl)
} else {
// If replace ends up replacing the full url it'll be `undefined`, meaning we have to default it to `/`
parsedUrl.pathname =
parsedUrl.pathname!.replace(
this.nextConfig.experimental.basePath,
''
) || '/'
req.url = req.url!.replace(this.nextConfig.experimental.basePath, '')
parsedUrl.pathname = parsedUrl.pathname!.replace(basePath, '') || '/'
req.url = req.url!.replace(basePath, '')
}

res.statusCode = 200
Expand Down
14 changes: 14 additions & 0 deletions test/integration/basepath/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* global jasmine */
import webdriver from 'next-webdriver'
import { join } from 'path'
import url from 'url'
import {
nextServer,
launchApp,
Expand Down Expand Up @@ -49,6 +50,19 @@ const runTests = (context, dev = false) => {
}
})

it('should have correct href for a link', async () => {
const browser = await webdriver(context.appPort, '/docs/hello')
const href = await browser.elementByCss('a').getAttribute('href')
const { pathname } = url.parse(href)
expect(pathname).toBe('/docs/other-page')
})

it('should show 404 for page not under the /docs prefix', async () => {
const text = await renderViaHTTP(context.appPort, '/hello')
expect(text).not.toContain('Hello World')
expect(text).toContain('This page could not be found')
})

it('should show the other-page page under the /docs prefix', async () => {
const browser = await webdriver(context.appPort, '/docs/other-page')
try {
Expand Down

0 comments on commit 32c26be

Please sign in to comment.