diff --git a/packages/next/client/components/navigation.ts b/packages/next/client/components/navigation.ts
index 0d5f4dde633feb4..37423db9f080e58 100644
--- a/packages/next/client/components/navigation.ts
+++ b/packages/next/client/components/navigation.ts
@@ -76,7 +76,6 @@ export function useSearchParams() {
throw new Error('invariant expected search params to be mounted')
}
- // eslint-disable-next-line react-hooks/rules-of-hooks
const readonlySearchParams = useMemo(() => {
return new ReadonlyURLSearchParams(searchParams)
}, [searchParams])
@@ -87,14 +86,9 @@ export function useSearchParams() {
/**
* Get the current pathname. For example usePathname() on /dashboard?foo=bar would return "/dashboard"
*/
-export function usePathname(): string {
+export function usePathname(): string | null {
staticGenerationBailout('usePathname')
- const pathname = useContext(PathnameContext)
- if (pathname === null) {
- throw new Error('invariant expected pathname to be mounted')
- }
-
- return pathname
+ return useContext(PathnameContext)
}
// TODO-APP: getting all params when client-side navigating is non-trivial as it does not have route matchers so this might have to be a server context instead.
diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx
index a63a5ca9b05c911..a5baeae1648c634 100644
--- a/packages/next/client/index.tsx
+++ b/packages/next/client/index.tsx
@@ -39,13 +39,10 @@ import { hasBasePath } from './has-base-path'
import { AppRouterContext } from '../shared/lib/app-router-context'
import {
adaptForAppRouterInstance,
- adaptForPathname,
adaptForSearchParams,
+ PathnameContextProviderAdapter,
} from '../shared/lib/router/adapters'
-import {
- PathnameContext,
- SearchParamsContext,
-} from '../shared/lib/hooks-client-context'
+import { SearchParamsContext } from '../shared/lib/hooks-client-context'
///
@@ -316,7 +313,10 @@ function AppContainer({
>
-
+
-
+
diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx
index b379c3d9a8a70fa..c5b2686a6e2b5f9 100644
--- a/packages/next/server/render.tsx
+++ b/packages/next/server/render.tsx
@@ -84,14 +84,11 @@ import stripAnsi from 'next/dist/compiled/strip-ansi'
import { stripInternalQueries } from './internal-utils'
import {
adaptForAppRouterInstance,
- adaptForPathname,
adaptForSearchParams,
+ PathnameContextProviderAdapter,
} from '../shared/lib/router/adapters'
import { AppRouterContext } from '../shared/lib/app-router-context'
-import {
- PathnameContext,
- SearchParamsContext,
-} from '../shared/lib/hooks-client-context'
+import { SearchParamsContext } from '../shared/lib/hooks-client-context'
let tryGetPreviewData: typeof import('./api-utils/node').tryGetPreviewData
let warn: typeof import('../build/output/log').warn
@@ -621,7 +618,10 @@ export async function renderToHTML(
const AppContainer = ({ children }: { children: JSX.Element }) => (
-
+
-
+
)
diff --git a/packages/next/shared/lib/router/adapters.ts b/packages/next/shared/lib/router/adapters.ts
deleted file mode 100644
index facd64c2a7effed..000000000000000
--- a/packages/next/shared/lib/router/adapters.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import type { ParsedUrlQuery } from 'node:querystring'
-import { AppRouterInstance } from '../app-router-context'
-import { NextRouter } from './router'
-
-/**
- * adaptForAppRouterInstance implements the AppRouterInstance with a NextRouter.
- *
- * @param router the NextRouter to adapt
- * @returns an AppRouterInstance
- */
-export function adaptForAppRouterInstance(
- router: NextRouter
-): AppRouterInstance {
- return {
- back(): void {
- router.back()
- },
- forward(): void {
- router.forward()
- },
- refresh(): void {
- router.reload()
- },
- push(href: string): void {
- void router.push(href)
- },
- replace(href: string): void {
- void router.replace(href)
- },
- prefetch(href: string): void {
- void router.prefetch(href)
- },
- }
-}
-
-/**
- * transforms the ParsedUrlQuery into a URLSearchParams.
- *
- * @param query the query to transform
- * @returns URLSearchParams
- */
-function transformQuery(query: ParsedUrlQuery): URLSearchParams {
- const params = new URLSearchParams()
-
- for (const [name, value] of Object.entries(query)) {
- if (Array.isArray(value)) {
- for (const val of value) {
- params.append(name, val)
- }
- } else if (typeof value !== 'undefined') {
- params.append(name, value)
- }
- }
-
- return params
-}
-
-/**
- * adaptForSearchParams transforms the ParsedURLQuery into URLSearchParams.
- *
- * @param router the router that contains the query.
- * @returns the search params in the URLSearchParams format
- */
-export function adaptForSearchParams(router: NextRouter): URLSearchParams {
- if (!router.isReady || !router.query) {
- return new URLSearchParams()
- }
-
- return transformQuery(router.query)
-}
-
-/**
- * adaptForPathname adapts the `asPath` parameter from the router to a pathname.
- *
- * @param asPath the asPath parameter to transform that comes from the router
- * @returns pathname part of `asPath`
- */
-export function adaptForPathname(asPath: string): string {
- const url = new URL(asPath, 'http://f')
- return url.pathname
-}
diff --git a/packages/next/shared/lib/router/adapters.tsx b/packages/next/shared/lib/router/adapters.tsx
new file mode 100644
index 000000000000000..9bf0096abad2be7
--- /dev/null
+++ b/packages/next/shared/lib/router/adapters.tsx
@@ -0,0 +1,129 @@
+import type { ParsedUrlQuery } from 'node:querystring'
+import React, { useMemo, useRef } from 'react'
+import type { AppRouterInstance } from '../app-router-context'
+import { PathnameContext } from '../hooks-client-context'
+import type { NextRouter } from './router'
+import { isDynamicRoute } from './utils'
+
+/**
+ * adaptForAppRouterInstance implements the AppRouterInstance with a NextRouter.
+ *
+ * @param router the NextRouter to adapt
+ * @returns an AppRouterInstance
+ */
+export function adaptForAppRouterInstance(
+ router: NextRouter
+): AppRouterInstance {
+ return {
+ back(): void {
+ router.back()
+ },
+ forward(): void {
+ router.forward()
+ },
+ refresh(): void {
+ router.reload()
+ },
+ push(href: string): void {
+ void router.push(href)
+ },
+ replace(href: string): void {
+ void router.replace(href)
+ },
+ prefetch(href: string): void {
+ void router.prefetch(href)
+ },
+ }
+}
+
+/**
+ * transforms the ParsedUrlQuery into a URLSearchParams.
+ *
+ * @param query the query to transform
+ * @returns URLSearchParams
+ */
+function transformQuery(query: ParsedUrlQuery): URLSearchParams {
+ const params = new URLSearchParams()
+
+ for (const [name, value] of Object.entries(query)) {
+ if (Array.isArray(value)) {
+ for (const val of value) {
+ params.append(name, val)
+ }
+ } else if (typeof value !== 'undefined') {
+ params.append(name, value)
+ }
+ }
+
+ return params
+}
+
+/**
+ * adaptForSearchParams transforms the ParsedURLQuery into URLSearchParams.
+ *
+ * @param router the router that contains the query.
+ * @returns the search params in the URLSearchParams format
+ */
+export function adaptForSearchParams(
+ router: Pick
+): URLSearchParams {
+ if (!router.isReady || !router.query) {
+ return new URLSearchParams()
+ }
+
+ return transformQuery(router.query)
+}
+
+export function PathnameContextProviderAdapter({
+ children,
+ router,
+ ...props
+}: React.PropsWithChildren<{
+ router: Pick
+ isAutoExport: boolean
+}>) {
+ const ref = useRef(props.isAutoExport)
+ const value = useMemo(() => {
+ // isAutoExport is only ever `true` on the first render from the server,
+ // so reset it to `false` after we read it for the first time as `true`. If
+ // we don't use the value, then we don't need it.
+ const isAutoExport = ref.current
+ if (isAutoExport) {
+ ref.current = false
+ }
+
+ // When the route is a dynamic route, we need to do more processing to
+ // determine if we need to stop showing the pathname.
+ if (isDynamicRoute(router.pathname)) {
+ // When the router is rendering the fallback page, it can't possibly know
+ // the path, so return `null` here. Read more about fallback pages over
+ // at:
+ // https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-pages
+ if (router.isFallback) {
+ return null
+ }
+
+ // When `isAutoExport` is true, meaning this is a page page has been
+ // automatically statically optimized, and the router is not ready, then
+ // we can't know the pathname yet. Read more about automatic static
+ // optimization at:
+ // https://nextjs.org/docs/advanced-features/automatic-static-optimization
+ if (isAutoExport && !router.isReady) {
+ return null
+ }
+ }
+
+ // The `router.asPath` contains the pathname seen by the browser (including
+ // any query strings), so it should have that stripped. Read more about the
+ // `asPath` option over at:
+ // https://nextjs.org/docs/api-reference/next/router#router-object
+ const url = new URL(router.asPath, 'http://f')
+ return url.pathname
+ }, [router.asPath, router.isFallback, router.isReady, router.pathname])
+
+ return (
+
+ {children}
+
+ )
+}