next.js/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts
refresh-reducer.ts103 lines3.8 KB
import type {
  ReadonlyReducerState,
  ReducerState,
  RefreshAction,
} from '../router-reducer-types'
import { ScrollBehavior } from '../router-reducer-types'
import {
  convertServerPatchToFullTree,
  navigateToKnownRoute,
} from '../../segment-cache/navigation'
import { invalidateSegmentCacheEntries } from '../../segment-cache/cache'
import { hasInterceptionRouteInCurrentTree } from './has-interception-route-in-current-tree'
import { FreshnessPolicy } from '../ppr-navigations'
import {
  invalidateBfCache,
  UnknownDynamicStaleTime,
} from '../../segment-cache/bfcache'

export function refreshReducer(
  state: ReadonlyReducerState,
  action: RefreshAction
): ReducerState {
  // During a refresh, we invalidate the segment cache but not the route cache.
  // The route cache contains the tree structure (which segments exist at a
  // given URL) which doesn't change during a refresh. The segment cache
  // contains the actual RSC data which needs to be re-fetched.
  //
  // The Instant Navigation Testing API can bypass cache invalidation to
  // preserve prefetched data when refreshing after an MPA navigation. This is
  // only used for testing and is not exposed in production builds by default.
  const bypassCacheInvalidation =
    process.env.__NEXT_EXPOSE_TESTING_API && action.bypassCacheInvalidation
  if (!bypassCacheInvalidation) {
    const currentNextUrl = state.nextUrl
    const currentRouterState = state.tree
    invalidateSegmentCacheEntries(currentNextUrl, currentRouterState)
  }
  return refreshDynamicData(state, FreshnessPolicy.RefreshAll)
}

export function refreshDynamicData(
  state: ReadonlyReducerState,
  freshnessPolicy: FreshnessPolicy.RefreshAll | FreshnessPolicy.HMRRefresh
): ReducerState {
  // During a refresh, invalidate the BFCache, which may contain dynamic data.
  invalidateBfCache()

  const currentNextUrl = state.nextUrl

  // We always send the last next-url, not the current when performing a dynamic
  // request. This is because we update the next-url after a navigation, but we
  // want the same interception route to be matched that used the last next-url.
  const nextUrlForRefresh = hasInterceptionRouteInCurrentTree(state.tree)
    ? state.previousNextUrl || currentNextUrl
    : null

  // A refresh is modeled as a navigation to the current URL, but where any
  // existing dynamic data (including in shared layouts) is re-fetched.
  const currentCanonicalUrl = state.canonicalUrl
  const currentUrl = new URL(currentCanonicalUrl, location.origin)
  const currentRenderedSearch = state.renderedSearch
  const currentFlightRouterState = state.tree
  const scrollBehavior = ScrollBehavior.NoScroll

  // Create a NavigationSeed from the current FlightRouterState.
  // TODO: Eventually we will store this type directly on the state object
  // instead of reconstructing it on demand. Part of a larger series of
  // refactors to unify the various tree types that the client deals with.
  const now = Date.now()
  // TODO: Store the dynamic stale time on the top-level state so it's known
  // during restores and refreshes.
  const refreshSeed = convertServerPatchToFullTree(
    now,
    currentFlightRouterState,
    null,
    currentRenderedSearch,
    UnknownDynamicStaleTime
  )

  const navigateType = 'replace'
  return navigateToKnownRoute(
    now,
    state,
    currentUrl,
    currentCanonicalUrl,
    refreshSeed,
    currentUrl,
    currentRenderedSearch,
    state.cache,
    currentFlightRouterState,
    freshnessPolicy,
    nextUrlForRefresh,
    scrollBehavior,
    navigateType,
    null,
    // Refresh navigations don't use route prediction, so there's no route
    // cache entry to mark as having a dynamic rewrite on mismatch. If a
    // mismatch occurs, the retry handler will traverse the known route tree
    // to find and mark the entry.
    null
  )
}
Quest for Codev2.0.0
/
SIGN IN