next
Version:
The React Framework
229 lines (228 loc) • 9.14 kB
TypeScript
import type { CacheNode } from '../../../shared/lib/app-router-context.shared-runtime';
import type { FlightRouterState, FlightSegmentPath } from '../../../server/app-render/types';
import type { FetchServerResponseResult } from './fetch-server-response';
export declare const ACTION_REFRESH = "refresh";
export declare const ACTION_NAVIGATE = "navigate";
export declare const ACTION_RESTORE = "restore";
export declare const ACTION_SERVER_PATCH = "server-patch";
export declare const ACTION_PREFETCH = "prefetch";
export declare const ACTION_HMR_REFRESH = "hmr-refresh";
export declare const ACTION_SERVER_ACTION = "server-action";
export type RouterChangeByServerResponse = ({ navigatedAt, previousTree, serverResponse, }: {
navigatedAt: number;
previousTree: FlightRouterState;
serverResponse: FetchServerResponseResult;
}) => void;
export interface Mutable {
mpaNavigation?: boolean;
patchedTree?: FlightRouterState;
canonicalUrl?: string;
scrollableSegments?: FlightSegmentPath[];
pendingPush?: boolean;
cache?: CacheNode;
prefetchCache?: AppRouterState['prefetchCache'];
hashFragment?: string;
shouldScroll?: boolean;
preserveCustomHistoryState?: boolean;
onlyHashChange?: boolean;
}
export interface ServerActionMutable extends Mutable {
inFlightServerAction?: Promise<any> | null;
}
/**
* Refresh triggers a refresh of the full page data.
* - fetches the Flight data and fills rsc at the root of the cache.
* - The router state is updated at the root.
*/
export interface RefreshAction {
type: typeof ACTION_REFRESH;
origin: Location['origin'];
}
export interface HmrRefreshAction {
type: typeof ACTION_HMR_REFRESH;
origin: Location['origin'];
}
export type ServerActionDispatcher = (args: Omit<ServerActionAction, 'type' | 'mutable' | 'navigate' | 'changeByServerResponse' | 'cache'>) => void;
export interface ServerActionAction {
type: typeof ACTION_SERVER_ACTION;
actionId: string;
actionArgs: any[];
resolve: (value: any) => void;
reject: (reason?: any) => void;
}
/**
* Navigate triggers a navigation to the provided url. It supports two types: `push` and `replace`.
*
* `navigateType`:
* - `push` - pushes a new history entry in the browser history
* - `replace` - replaces the current history entry in the browser history
*
* Navigate has multiple cache heuristics:
* - page was prefetched
* - Apply router state tree from prefetch
* - Apply Flight data from prefetch to the cache
* - If Flight data is a string, it's a redirect and the state is updated to trigger a redirect
* - Check if hard navigation is needed
* - Hard navigation happens when a dynamic parameter below the common layout changed
* - When hard navigation is needed the cache is invalidated below the flightSegmentPath
* - The missing cache nodes of the page will be fetched in layout-router and trigger the SERVER_PATCH action
* - If hard navigation is not needed
* - The cache is reused
* - If any cache nodes are missing they'll be fetched in layout-router and trigger the SERVER_PATCH action
* - page was not prefetched
* - The navigate was called from `next/router` (`router.push()` / `router.replace()`) / `next/link` without prefetched data available (e.g. the prefetch didn't come back from the server before clicking the link)
* - Flight data is fetched in the reducer (suspends the reducer)
* - Router state tree is created based on Flight data
* - Cache is filled based on the Flight data
*
* Above steps explain 3 cases:
* - `soft` - Reuses the existing cache and fetches missing nodes in layout-router.
* - `hard` - Creates a new cache where cache nodes are removed below the common layout and fetches missing nodes in layout-router.
* - `optimistic` (explicit no prefetch) - Creates a new cache and kicks off the data fetch in the reducer. The data fetch is awaited in the layout-router.
*/
export interface NavigateAction {
type: typeof ACTION_NAVIGATE;
url: URL;
isExternalUrl: boolean;
locationSearch: Location['search'];
navigateType: 'push' | 'replace';
shouldScroll: boolean;
allowAliasing: boolean;
}
/**
* Restore applies the provided router state.
* - Used for `popstate` (back/forward navigation) where a known router state has to be applied.
* - Also used when syncing the router state with `pushState`/`replaceState` calls.
* - Router state is applied as-is from the history state, if available.
* - If the history state does not contain the router state, the existing router state is used.
* - If any cache node is missing it will be fetched in layout-router during rendering and the server-patch case.
* - If existing cache nodes match these are used.
*/
export interface RestoreAction {
type: typeof ACTION_RESTORE;
url: URL;
tree: FlightRouterState | undefined;
}
/**
* Server-patch applies the provided Flight data to the cache and router tree.
* - Only triggered in layout-router.
* - Creates a new cache and router state with the Flight data applied.
*/
export interface ServerPatchAction {
type: typeof ACTION_SERVER_PATCH;
navigatedAt: number;
serverResponse: FetchServerResponseResult;
previousTree: FlightRouterState;
}
/**
* PrefetchKind defines the type of prefetching that should be done.
* - `auto` - if the page is dynamic, prefetch the page data partially, if static prefetch the page data fully.
* - `full` - prefetch the page data fully.
* - `temporary` - a temporary prefetch entry is added to the cache, this is used when prefetch={false} is used in next/link or when you push a route programmatically.
*/
export declare enum PrefetchKind {
AUTO = "auto",
FULL = "full",
TEMPORARY = "temporary"
}
/**
* Prefetch adds the provided FlightData to the prefetch cache
* - Creates the router state tree based on the patch in FlightData
* - Adds the FlightData to the prefetch cache
* - In ACTION_NAVIGATE the prefetch cache is checked and the router state tree and FlightData are applied.
*/
export interface PrefetchAction {
type: typeof ACTION_PREFETCH;
url: URL;
kind: PrefetchKind;
}
export interface PushRef {
/**
* If the app-router should push a new history entry in app-router's useEffect()
*/
pendingPush: boolean;
/**
* Multi-page navigation through location.href.
*/
mpaNavigation: boolean;
/**
* Skip applying the router state to the browser history state.
*/
preserveCustomHistoryState: boolean;
}
export type FocusAndScrollRef = {
/**
* If focus and scroll should be set in the layout-router's useEffect()
*/
apply: boolean;
/**
* The hash fragment that should be scrolled to.
*/
hashFragment: string | null;
/**
* The paths of the segments that should be focused.
*/
segmentPaths: FlightSegmentPath[];
/**
* If only the URLs hash fragment changed
*/
onlyHashChange: boolean;
};
export type PrefetchCacheEntry = {
treeAtTimeOfPrefetch: FlightRouterState;
data: Promise<FetchServerResponseResult>;
kind: PrefetchKind;
prefetchTime: number;
staleTime: number;
lastUsedTime: number | null;
key: string;
status: PrefetchCacheEntryStatus;
url: URL;
};
export declare enum PrefetchCacheEntryStatus {
fresh = "fresh",
reusable = "reusable",
expired = "expired",
stale = "stale"
}
/**
* Handles keeping the state of app-router.
*/
export type AppRouterState = {
/**
* The router state, this is written into the history state in app-router using replaceState/pushState.
* - Has to be serializable as it is written into the history state.
* - Holds which segments and parallel routes are shown on the screen.
*/
tree: FlightRouterState;
/**
* The cache holds React nodes for every segment that is shown on screen as well as previously shown segments.
* It also holds in-progress data requests.
* Prefetched data is stored separately in `prefetchCache`, that is applied during ACTION_NAVIGATE.
*/
cache: CacheNode;
/**
* Cache that holds prefetched Flight responses keyed by url.
*/
prefetchCache: Map<string, PrefetchCacheEntry>;
/**
* Decides if the update should create a new history entry and if the navigation has to trigger a browser navigation.
*/
pushRef: PushRef;
/**
* Decides if the update should apply scroll and focus management.
*/
focusAndScrollRef: FocusAndScrollRef;
/**
* The canonical url that is pushed/replaced.
* - This is the url you see in the browser.
*/
canonicalUrl: string;
/**
* The underlying "url" representing the UI state, which is used for intercepting routes.
*/
nextUrl: string | null;
};
export type ReadonlyReducerState = Readonly<AppRouterState>;
export type ReducerState = Promise<AppRouterState> | AppRouterState;
export type ReducerActions = Readonly<RefreshAction | NavigateAction | RestoreAction | ServerPatchAction | PrefetchAction | HmrRefreshAction | ServerActionAction>;