"use client"; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import React, { use, useEffect, useMemo, useCallback, startTransition, useInsertionEffect, useDeferredValue } from "react"; import { AppRouterContext, LayoutRouterContext, GlobalLayoutRouterContext, MissingSlotContext } from "../../shared/lib/app-router-context.shared-runtime"; import { ACTION_FAST_REFRESH, ACTION_NAVIGATE, ACTION_PREFETCH, ACTION_REFRESH, ACTION_RESTORE, ACTION_SERVER_ACTION, ACTION_SERVER_PATCH, PrefetchKind } from "./router-reducer/router-reducer-types"; import { createHrefFromUrl } from "./router-reducer/create-href-from-url"; import { SearchParamsContext, PathnameContext, PathParamsContext } from "../../shared/lib/hooks-client-context.shared-runtime"; import { useReducerWithReduxDevtools, useUnwrapState } from "./use-reducer-with-devtools"; import { ErrorBoundary } from "./error-boundary"; import { createInitialRouterState } from "./router-reducer/create-initial-router-state"; import { isBot } from "../../shared/lib/router/utils/is-bot"; import { addBasePath } from "../add-base-path"; import { AppRouterAnnouncer } from "./app-router-announcer"; import { RedirectBoundary } from "./redirect-boundary"; import { findHeadInCache } from "./router-reducer/reducers/find-head-in-cache"; import { unresolvedThenable } from "./unresolved-thenable"; import { NEXT_RSC_UNION_QUERY } from "./app-router-headers"; import { removeBasePath } from "../remove-base-path"; import { hasBasePath } from "../has-base-path"; import { PAGE_SEGMENT_KEY } from "../../shared/lib/segment"; const isServer = typeof window === "undefined"; // Ensure the initialParallelRoutes are not combined because of double-rendering in the browser with Strict Mode. let initialParallelRoutes = isServer ? null : new Map(); let globalServerActionDispatcher = null; export function getServerActionDispatcher() { return globalServerActionDispatcher; } const globalMutable = {}; export function urlToUrlWithoutFlightMarker(url) { const urlWithoutFlightParameters = new URL(url, location.origin); urlWithoutFlightParameters.searchParams.delete(NEXT_RSC_UNION_QUERY); if (process.env.NODE_ENV === "production") { if (process.env.__NEXT_CONFIG_OUTPUT === "export" && urlWithoutFlightParameters.pathname.endsWith(".txt")) { const { pathname } = urlWithoutFlightParameters; const length = pathname.endsWith("/index.txt") ? 10 : 4; // Slice off `/index.txt` or `.txt` from the end of the pathname urlWithoutFlightParameters.pathname = pathname.slice(0, -length); } } return urlWithoutFlightParameters; } // this function performs a depth-first search of the tree to find the selected // params function getSelectedParams(currentTree, params) { if (params === void 0) params = {}; const parallelRoutes = currentTree[1]; for (const parallelRoute of Object.values(parallelRoutes)){ const segment = parallelRoute[0]; const isDynamicParameter = Array.isArray(segment); const segmentValue = isDynamicParameter ? segment[1] : segment; if (!segmentValue || segmentValue.startsWith(PAGE_SEGMENT_KEY)) continue; // Ensure catchAll and optional catchall are turned into an array const isCatchAll = isDynamicParameter && (segment[2] === "c" || segment[2] === "oc"); if (isCatchAll) { params[segment[0]] = segment[1].split("/"); } else if (isDynamicParameter) { params[segment[0]] = segment[1]; } params = getSelectedParams(parallelRoute, params); } return params; } function isExternalURL(url) { return url.origin !== window.location.origin; } function HistoryUpdater(param) { let { appRouterState, sync } = param; useInsertionEffect(()=>{ const { tree, pushRef, canonicalUrl } = appRouterState; const historyState = { ...pushRef.preserveCustomHistoryState ? window.history.state : {}, // Identifier is shortened intentionally. // __NA is used to identify if the history entry can be handled by the app-router. // __N is used to identify if the history entry can be handled by the old router. __NA: true, __PRIVATE_NEXTJS_INTERNALS_TREE: tree }; if (pushRef.pendingPush && // Skip pushing an additional history entry if the canonicalUrl is the same as the current url. // This mirrors the browser behavior for normal navigation. createHrefFromUrl(new URL(window.location.href)) !== canonicalUrl) { // This intentionally mutates React state, pushRef is overwritten to ensure additional push/replace calls do not trigger an additional history entry. pushRef.pendingPush = false; window.history.pushState(historyState, "", canonicalUrl); } else { window.history.replaceState(historyState, "", canonicalUrl); } sync(appRouterState); }, [ appRouterState, sync ]); return null; } export function createEmptyCacheNode() { return { lazyData: null, rsc: null, prefetchRsc: null, head: null, prefetchHead: null, parallelRoutes: new Map(), lazyDataResolved: false, loading: null }; } function useServerActionDispatcher(dispatch) { const serverActionDispatcher = useCallback((actionPayload)=>{ startTransition(()=>{ dispatch({ ...actionPayload, type: ACTION_SERVER_ACTION }); }); }, [ dispatch ]); globalServerActionDispatcher = serverActionDispatcher; } /** * Server response that only patches the cache and tree. */ function useChangeByServerResponse(dispatch) { return useCallback((param)=>{ let { previousTree, serverResponse } = param; startTransition(()=>{ dispatch({ type: ACTION_SERVER_PATCH, previousTree, serverResponse }); }); }, [ dispatch ]); } function useNavigate(dispatch) { return useCallback((href, navigateType, shouldScroll)=>{ const url = new URL(addBasePath(href), location.href); return dispatch({ type: ACTION_NAVIGATE, url, isExternalUrl: isExternalURL(url), locationSearch: location.search, shouldScroll: shouldScroll != null ? shouldScroll : true, navigateType }); }, [ dispatch ]); } function copyNextJsInternalHistoryState(data) { if (data == null) data = {}; const currentState = window.history.state; const __NA = currentState == null ? void 0 : currentState.__NA; if (__NA) { data.__NA = __NA; } const __PRIVATE_NEXTJS_INTERNALS_TREE = currentState == null ? void 0 : currentState.__PRIVATE_NEXTJS_INTERNALS_TREE; if (__PRIVATE_NEXTJS_INTERNALS_TREE) { data.__PRIVATE_NEXTJS_INTERNALS_TREE = __PRIVATE_NEXTJS_INTERNALS_TREE; } return data; } function Head(param) { let { headCacheNode } = param; // If this segment has a `prefetchHead`, it's the statically prefetched data. // We should use that on initial render instead of `head`. Then we'll switch // to `head` when the dynamic response streams in. const head = headCacheNode !== null ? headCacheNode.head : null; const prefetchHead = headCacheNode !== null ? headCacheNode.prefetchHead : null; // If no prefetch data is available, then we go straight to rendering `head`. const resolvedPrefetchRsc = prefetchHead !== null ? prefetchHead : head; // We use `useDeferredValue` to handle switching between the prefetched and // final values. The second argument is returned on initial render, then it // re-renders with the first argument. // // @ts-expect-error The second argument to `useDeferredValue` is only // available in the experimental builds. When its disabled, it will always // return `head`. return useDeferredValue(head, resolvedPrefetchRsc); } /** * The global router that wraps the application components. */ function Router(param) { let { buildId, initialHead, initialTree, urlParts, initialSeedData, couldBeIntercepted, assetPrefix, missingSlots } = param; const initialState = useMemo(()=>createInitialRouterState({ buildId, initialSeedData, urlParts, initialTree, initialParallelRoutes, location: !isServer ? window.location : null, initialHead, couldBeIntercepted }), [ buildId, initialSeedData, urlParts, initialTree, initialHead, couldBeIntercepted ]); const [reducerState, dispatch, sync] = useReducerWithReduxDevtools(initialState); useEffect(()=>{ // Ensure initialParallelRoutes is cleaned up from memory once it's used. initialParallelRoutes = null; }, []); const { canonicalUrl } = useUnwrapState(reducerState); // Add memoized pathname/query for useSearchParams and usePathname. const { searchParams, pathname } = useMemo(()=>{ const url = new URL(canonicalUrl, typeof window === "undefined" ? "http://n" : window.location.href); return { // This is turned into a readonly class in `useSearchParams` searchParams: url.searchParams, pathname: hasBasePath(url.pathname) ? removeBasePath(url.pathname) : url.pathname }; }, [ canonicalUrl ]); const changeByServerResponse = useChangeByServerResponse(dispatch); const navigate = useNavigate(dispatch); useServerActionDispatcher(dispatch); /** * The app router that is exposed through `useRouter`. It's only concerned with dispatching actions to the reducer, does not hold state. */ const appRouter = useMemo(()=>{ const routerInstance = { back: ()=>window.history.back(), forward: ()=>window.history.forward(), prefetch: (href, options)=>{ // Don't prefetch for bots as they don't navigate. if (isBot(window.navigator.userAgent)) { return; } let url; try { url = new URL(addBasePath(href), window.location.href); } catch (_) { throw new Error("Cannot prefetch '" + href + "' because it cannot be converted to a URL."); } // Don't prefetch during development (improves compilation performance) if (process.env.NODE_ENV === "development") { return; } // External urls can't be prefetched in the same way. if (isExternalURL(url)) { return; } startTransition(()=>{ var _options_kind; dispatch({ type: ACTION_PREFETCH, url, kind: (_options_kind = options == null ? void 0 : options.kind) != null ? _options_kind : PrefetchKind.FULL }); }); }, replace: (href, options)=>{ if (options === void 0) options = {}; startTransition(()=>{ var _options_scroll; navigate(href, "replace", (_options_scroll = options.scroll) != null ? _options_scroll : true); }); }, push: (href, options)=>{ if (options === void 0) options = {}; startTransition(()=>{ var _options_scroll; navigate(href, "push", (_options_scroll = options.scroll) != null ? _options_scroll : true); }); }, refresh: ()=>{ startTransition(()=>{ dispatch({ type: ACTION_REFRESH, origin: window.location.origin }); }); }, fastRefresh: ()=>{ if (process.env.NODE_ENV !== "development") { throw new Error("fastRefresh can only be used in development mode. Please use refresh instead."); } else { startTransition(()=>{ dispatch({ type: ACTION_FAST_REFRESH, origin: window.location.origin }); }); } } }; return routerInstance; }, [ dispatch, navigate ]); useEffect(()=>{ // Exists for debugging purposes. Don't use in application code. if (window.next) { window.next.router = appRouter; } }, [ appRouter ]); if (process.env.NODE_ENV !== "production") { // eslint-disable-next-line react-hooks/rules-of-hooks const { cache, prefetchCache, tree } = useUnwrapState(reducerState); // This hook is in a conditional but that is ok because `process.env.NODE_ENV` never changes // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(()=>{ // Add `window.nd` for debugging purposes. // This is not meant for use in applications as concurrent rendering will affect the cache/tree/router. // @ts-ignore this is for debugging window.nd = { router: appRouter, cache, prefetchCache, tree }; }, [ appRouter, cache, prefetchCache, tree ]); } useEffect(()=>{ // If the app is restored from bfcache, it's possible that // pushRef.mpaNavigation is true, which would mean that any re-render of this component // would trigger the mpa navigation logic again from the lines below. // This will restore the router to the initial state in the event that the app is restored from bfcache. function handlePageShow(event) { var _window_history_state; if (!event.persisted || !((_window_history_state = window.history.state) == null ? void 0 : _window_history_state.__PRIVATE_NEXTJS_INTERNALS_TREE)) { return; } // Clear the pendingMpaPath value so that a subsequent MPA navigation to the same URL can be triggered. // This is necessary because if the browser restored from bfcache, the pendingMpaPath would still be set to the value // of the last MPA navigation. globalMutable.pendingMpaPath = undefined; dispatch({ type: ACTION_RESTORE, url: new URL(window.location.href), tree: window.history.state.__PRIVATE_NEXTJS_INTERNALS_TREE }); } window.addEventListener("pageshow", handlePageShow); return ()=>{ window.removeEventListener("pageshow", handlePageShow); }; }, [ dispatch ]); // When mpaNavigation flag is set do a hard navigation to the new url. // Infinitely suspend because we don't actually want to rerender any child // components with the new URL and any entangled state updates shouldn't // commit either (eg: useTransition isPending should stay true until the page // unloads). // // This is a side effect in render. Don't try this at home, kids. It's // probably safe because we know this is a singleton component and it's never // in . At least I hope so. (It will run twice in dev strict mode, // but that's... fine?) const { pushRef } = useUnwrapState(reducerState); if (pushRef.mpaNavigation) { // if there's a re-render, we don't want to trigger another redirect if one is already in flight to the same URL if (globalMutable.pendingMpaPath !== canonicalUrl) { const location1 = window.location; if (pushRef.pendingPush) { location1.assign(canonicalUrl); } else { location1.replace(canonicalUrl); } globalMutable.pendingMpaPath = canonicalUrl; } // TODO-APP: Should we listen to navigateerror here to catch failed // navigations somehow? And should we call window.stop() if a SPA navigation // should interrupt an MPA one? use(unresolvedThenable); } useEffect(()=>{ const originalPushState = window.history.pushState.bind(window.history); const originalReplaceState = window.history.replaceState.bind(window.history); // Ensure the canonical URL in the Next.js Router is updated when the URL is changed so that `usePathname` and `useSearchParams` hold the pushed values. const applyUrlFromHistoryPushReplace = (url)=>{ var _window_history_state; const href = window.location.href; const tree = (_window_history_state = window.history.state) == null ? void 0 : _window_history_state.__PRIVATE_NEXTJS_INTERNALS_TREE; startTransition(()=>{ dispatch({ type: ACTION_RESTORE, url: new URL(url != null ? url : href, href), tree }); }); }; /** * Patch pushState to ensure external changes to the history are reflected in the Next.js Router. * Ensures Next.js internal history state is copied to the new history entry. * Ensures usePathname and useSearchParams hold the newly provided url. */ window.history.pushState = function pushState(data, _unused, url) { // Avoid a loop when Next.js internals trigger pushState/replaceState if ((data == null ? void 0 : data.__NA) || (data == null ? void 0 : data._N)) { return originalPushState(data, _unused, url); } data = copyNextJsInternalHistoryState(data); if (url) { applyUrlFromHistoryPushReplace(url); } return originalPushState(data, _unused, url); }; /** * Patch replaceState to ensure external changes to the history are reflected in the Next.js Router. * Ensures Next.js internal history state is copied to the new history entry. * Ensures usePathname and useSearchParams hold the newly provided url. */ window.history.replaceState = function replaceState(data, _unused, url) { // Avoid a loop when Next.js internals trigger pushState/replaceState if ((data == null ? void 0 : data.__NA) || (data == null ? void 0 : data._N)) { return originalReplaceState(data, _unused, url); } data = copyNextJsInternalHistoryState(data); if (url) { applyUrlFromHistoryPushReplace(url); } return originalReplaceState(data, _unused, url); }; /** * Handle popstate event, this is used to handle back/forward in the browser. * By default dispatches ACTION_RESTORE, however if the history entry was not pushed/replaced by app-router it will reload the page. * That case can happen when the old router injected the history entry. */ const onPopState = (param)=>{ let { state } = param; if (!state) { // TODO-APP: this case only happens when pushState/replaceState was called outside of Next.js. It should probably reload the page in this case. return; } // This case happens when the history entry was pushed by the `pages` router. if (!state.__NA) { window.location.reload(); return; } // TODO-APP: Ideally the back button should not use startTransition as it should apply the updates synchronously // Without startTransition works if the cache is there for this path startTransition(()=>{ dispatch({ type: ACTION_RESTORE, url: new URL(window.location.href), tree: state.__PRIVATE_NEXTJS_INTERNALS_TREE }); }); }; // Register popstate event to call onPopstate. window.addEventListener("popstate", onPopState); return ()=>{ window.history.pushState = originalPushState; window.history.replaceState = originalReplaceState; window.removeEventListener("popstate", onPopState); }; }, [ dispatch ]); const { cache, tree, nextUrl, focusAndScrollRef } = useUnwrapState(reducerState); const matchingHead = useMemo(()=>{ return findHeadInCache(cache, tree[1]); }, [ cache, tree ]); // Add memoized pathParams for useParams. const pathParams = useMemo(()=>{ return getSelectedParams(tree); }, [ tree ]); let head; if (matchingHead !== null) { // The head is wrapped in an extra component so we can use // `useDeferredValue` to swap between the prefetched and final versions of // the head. (This is what LayoutRouter does for segment data, too.) // // The `key` is used to remount the component whenever the head moves to // a different segment. const [headCacheNode, headKey] = matchingHead; head = /*#__PURE__*/ _jsx(Head, { headCacheNode: headCacheNode }, headKey); } else { head = null; } let content = /*#__PURE__*/ _jsxs(RedirectBoundary, { children: [ head, cache.rsc, /*#__PURE__*/ _jsx(AppRouterAnnouncer, { tree: tree }) ] }); if (process.env.NODE_ENV !== "production") { if (typeof window !== "undefined") { const DevRootNotFoundBoundary = require("./dev-root-not-found-boundary").DevRootNotFoundBoundary; content = /*#__PURE__*/ _jsx(DevRootNotFoundBoundary, { children: /*#__PURE__*/ _jsx(MissingSlotContext.Provider, { value: missingSlots, children: content }) }); } const HotReloader = require("./react-dev-overlay/app/hot-reloader-client").default; content = /*#__PURE__*/ _jsx(HotReloader, { assetPrefix: assetPrefix, children: content }); } return /*#__PURE__*/ _jsxs(_Fragment, { children: [ /*#__PURE__*/ _jsx(HistoryUpdater, { appRouterState: useUnwrapState(reducerState), sync: sync }), /*#__PURE__*/ _jsx(PathParamsContext.Provider, { value: pathParams, children: /*#__PURE__*/ _jsx(PathnameContext.Provider, { value: pathname, children: /*#__PURE__*/ _jsx(SearchParamsContext.Provider, { value: searchParams, children: /*#__PURE__*/ _jsx(GlobalLayoutRouterContext.Provider, { value: { buildId, changeByServerResponse, tree, focusAndScrollRef, nextUrl }, children: /*#__PURE__*/ _jsx(AppRouterContext.Provider, { value: appRouter, children: /*#__PURE__*/ _jsx(LayoutRouterContext.Provider, { value: { childNodes: cache.parallelRoutes, tree, // Root node always has `url` // Provided in AppTreeContext to ensure it can be overwritten in layout-router url: canonicalUrl, loading: cache.loading }, children: content }) }) }) }) }) }) ] }); } export default function AppRouter(props) { const { globalErrorComponent, ...rest } = props; return /*#__PURE__*/ _jsx(ErrorBoundary, { errorComponent: globalErrorComponent, children: /*#__PURE__*/ _jsx(Router, { ...rest }) }); } //# sourceMappingURL=app-router.js.map