diff --git a/docs/01-app/04-api-reference/04-functions/use-router.mdx b/docs/01-app/04-api-reference/04-functions/use-router.mdx index f4a167eca8339..0e8b36960e88c 100644 --- a/docs/01-app/04-api-reference/04-functions/use-router.mdx +++ b/docs/01-app/04-api-reference/04-functions/use-router.mdx @@ -47,6 +47,7 @@ export default function Page() { - `router.prefetch(href: string)`: [Prefetch](/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching) the provided route for faster client-side transitions. - `router.back()`: Navigate back to the previous route in the browser’s history stack. - `router.forward()`: Navigate forwards to the next page in the browser’s history stack. +- `router.basePath`: The active [basePath](/docs/app/api-reference/config/next-config-js/basePath) (if enabled). > **Good to know**: > diff --git a/docs/02-pages/03-api-reference/03-functions/use-router.mdx b/docs/02-pages/03-api-reference/03-functions/use-router.mdx index ed2c9d2664a8b..f4a4e9ff51772 100644 --- a/docs/02-pages/03-api-reference/03-functions/use-router.mdx +++ b/docs/02-pages/03-api-reference/03-functions/use-router.mdx @@ -40,7 +40,7 @@ The following is the definition of the `router` object returned by both [`useRou - `query`: `Object` - The query string parsed to an object, including [dynamic route](/docs/pages/building-your-application/routing/dynamic-routes) parameters. It will be an empty object during prerendering if the page doesn't use [Server-side Rendering](/docs/pages/building-your-application/data-fetching/get-server-side-props). Defaults to `{}` - `asPath`: `String` - The path as shown in the browser including the search params and respecting the `trailingSlash` configuration. `basePath` and `locale` are not included. - `isFallback`: `boolean` - Whether the current page is in [fallback mode](/docs/pages/api-reference/functions/get-static-paths#fallback-true). -- `basePath`: `String` - The active [basePath](/docs/app/api-reference/config/next-config-js/basePath) (if enabled). +- `basePath`: `String` - The active [basePath](/docs/pages/api-reference/config/next-config-js/basePath) (if enabled). - `locale`: `String` - The active locale (if enabled). - `locales`: `String[]` - All supported locales (if enabled). - `defaultLocale`: `String` - The current default locale (if enabled). diff --git a/packages/next/src/client/app-index.tsx b/packages/next/src/client/app-index.tsx index 1bf4acf3a4d78..5e3a1d9dca212 100644 --- a/packages/next/src/client/app-index.tsx +++ b/packages/next/src/client/app-index.tsx @@ -190,6 +190,7 @@ function ServerRoot(): React.ReactNode { actionQueue={actionQueue} globalErrorComponentAndStyles={initialRSCPayload.G} assetPrefix={initialRSCPayload.p} + basePath={initialRSCPayload.a} /> ) diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index 32e407f65842b..0107885ccb4fc 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -242,9 +242,11 @@ function Head({ function Router({ actionQueue, assetPrefix, + basePath, }: { actionQueue: AppRouterActionQueue assetPrefix: string + basePath: string }) { const [state, dispatch] = useReducer(actionQueue) const { canonicalUrl } = useUnwrapState(state) @@ -330,10 +332,11 @@ function Router({ }) } }, + basePath, } return routerInstance - }, [actionQueue, dispatch, navigate]) + }, [actionQueue, dispatch, navigate, basePath]) useEffect(() => { // Exists for debugging purposes. Don't use in application code. @@ -651,10 +654,12 @@ export default function AppRouter({ actionQueue, globalErrorComponentAndStyles: [globalErrorComponent, globalErrorStyles], assetPrefix, + basePath, }: { actionQueue: AppRouterActionQueue globalErrorComponentAndStyles: [ErrorComponent, React.ReactNode | undefined] assetPrefix: string + basePath: string }) { useNavFailureHandler() @@ -663,7 +668,11 @@ export default function AppRouter({ errorComponent={globalErrorComponent} errorStyles={globalErrorStyles} > - + ) } diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index eb16ab27416a9..83f6474744857 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -218,6 +218,7 @@ export type AppRenderContext = { pagePath: string clientReferenceManifest: DeepReadonly assetPrefix: string + basePath: string isNotFoundPath: boolean nonce: string | undefined res: BaseNextResponse @@ -845,6 +846,7 @@ async function getRSCPayload( P: , b: ctx.sharedContext.buildId, p: ctx.assetPrefix, + a: ctx.basePath, c: prepareInitialCanonicalUrl(url), i: !!couldBeIntercepted, f: [ @@ -964,6 +966,7 @@ async function getErrorRSCPayload( return { b: ctx.sharedContext.buildId, p: ctx.assetPrefix, + a: ctx.basePath, c: prepareInitialCanonicalUrl(url), m: undefined, i: false, @@ -1033,6 +1036,7 @@ function App({ actionQueue={actionQueue} globalErrorComponentAndStyles={response.G} assetPrefix={response.p} + basePath={response.a} /> @@ -1081,6 +1085,7 @@ function AppWithoutContext({ actionQueue={actionQueue} globalErrorComponentAndStyles={response.G} assetPrefix={response.p} + basePath={response.a} /> ) } @@ -1124,6 +1129,7 @@ async function renderToHTMLOrFlightImpl( nextFontManifest, serverActions, assetPrefix = '', + basePath = '', enableTainting, } = renderOpts @@ -1273,6 +1279,7 @@ async function renderToHTMLOrFlightImpl( nonce, res, sharedContext, + basePath, } getTracer().setRootSpanAttribute('next.route', pagePath) diff --git a/packages/next/src/server/app-render/types.ts b/packages/next/src/server/app-render/types.ts index 8046de212cd7b..c95b894f7c5ab 100644 --- a/packages/next/src/server/app-render/types.ts +++ b/packages/next/src/server/app-render/types.ts @@ -258,6 +258,8 @@ export type InitialRSCPayload = { b: string /** assetPrefix */ p: string + /** basePath */ + a: string /** initialCanonicalUrlParts */ c: string[] /** couldBeIntercepted */ diff --git a/packages/next/src/shared/lib/app-router-context.shared-runtime.ts b/packages/next/src/shared/lib/app-router-context.shared-runtime.ts index ff273f669bbc4..9f738df383dc1 100644 --- a/packages/next/src/shared/lib/app-router-context.shared-runtime.ts +++ b/packages/next/src/shared/lib/app-router-context.shared-runtime.ts @@ -148,6 +148,10 @@ export interface AppRouterInstance { * Prefetch the provided href. */ prefetch(href: string, options?: PrefetchOptions): void + /** + * The configured app's basePath. + */ + basePath: string } export const AppRouterContext = React.createContext( diff --git a/packages/next/src/shared/lib/router/adapters.test.tsx b/packages/next/src/shared/lib/router/adapters.test.tsx index 1120d008dcc1d..5d2314cf9c083 100644 --- a/packages/next/src/shared/lib/router/adapters.test.tsx +++ b/packages/next/src/shared/lib/router/adapters.test.tsx @@ -11,6 +11,7 @@ describe('adaptForAppRouterInstance', () => { push: jest.fn(), replace: jest.fn(), prefetch: jest.fn(), + basePath: '/base-path', } as unknown as NextRouter const adapter = adaptForAppRouterInstance(router) @@ -62,4 +63,9 @@ describe('adaptForAppRouterInstance', () => { adapter.prefetch('/foo') expect(router.prefetch).toHaveBeenCalledWith('/foo') }) + + it('should forward the basePath to `basePath`', () => { + const basePath = adapter.basePath + expect(basePath).toEqual('/base-path') + }) }) diff --git a/packages/next/src/shared/lib/router/adapters.tsx b/packages/next/src/shared/lib/router/adapters.tsx index cc3e34b3dfd04..4fc1dbb056a17 100644 --- a/packages/next/src/shared/lib/router/adapters.tsx +++ b/packages/next/src/shared/lib/router/adapters.tsx @@ -32,6 +32,7 @@ export function adaptForAppRouterInstance( prefetch(href) { void pagesRouter.prefetch(href) }, + basePath: pagesRouter.basePath, } } diff --git a/test/e2e/app-dir/app-basepath/app/page.js b/test/e2e/app-dir/app-basepath/app/page.js index 852f704e6c0e4..e031f077aaf99 100644 --- a/test/e2e/app-dir/app-basepath/app/page.js +++ b/test/e2e/app-dir/app-basepath/app/page.js @@ -1,9 +1,15 @@ +'use client' + import Link from 'next/link' +import { useRouter } from 'next/navigation' export default function Page() { + const router = useRouter() + return (

Test Page

+

{router.basePath}

Go to page 2
) diff --git a/test/e2e/app-dir/app-basepath/index.test.ts b/test/e2e/app-dir/app-basepath/index.test.ts index f7161723f8254..3fbbede56db96 100644 --- a/test/e2e/app-dir/app-basepath/index.test.ts +++ b/test/e2e/app-dir/app-basepath/index.test.ts @@ -21,6 +21,11 @@ describe('app dir - basepath', () => { expect(html).toContain('

Test Page

') }) + it('useRouter should correctly return `basePath`', async () => { + const html = await next.render('/base') + expect(html).toContain('

/base

') + }) + it('should support Link with basePath prefixed', async () => { const browser = await next.browser('/base') expect(