Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/next-core/src/next_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,7 @@ pub struct ExperimentalConfig {
server_source_maps: Option<bool>,
swc_trace_profiling: Option<bool>,
transition_indicator: Option<bool>,
gesture_transition: Option<bool>,
/// @internal Used by the Next.js internals only.
trust_host_header: Option<bool>,

Expand Down Expand Up @@ -1782,6 +1783,11 @@ impl NextConfig {
Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
}

#[turbo_tasks::function]
pub fn enable_gesture_transition(&self) -> Vc<bool> {
Vc::cell(self.experimental.gesture_transition.unwrap_or(false))
}

#[turbo_tasks::function]
pub fn enable_cache_components(&self) -> Vc<bool> {
Vc::cell(self.cache_components.unwrap_or(false))
Expand Down
6 changes: 4 additions & 2 deletions crates/next-core/src/next_import_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ pub async fn get_next_client_import_map(
// Keep in sync with file:///./../../../packages/next/src/lib/needs-experimental-react.ts
let taint = *next_config.enable_taint().await?;
let transition_indicator = *next_config.enable_transition_indicator().await?;
let react_channel = if taint || transition_indicator {
let gesture_transition = *next_config.enable_gesture_transition().await?;
let react_channel = if taint || transition_indicator || gesture_transition {
"-experimental"
} else {
""
Expand Down Expand Up @@ -834,7 +835,8 @@ async fn apply_vendored_react_aliases_server(
) -> Result<()> {
let taint = *next_config.enable_taint().await?;
let transition_indicator = *next_config.enable_transition_indicator().await?;
let react_channel = if taint || transition_indicator {
let gesture_transition = *next_config.enable_gesture_transition().await?;
let react_channel = if taint || transition_indicator || gesture_transition {
"-experimental"
} else {
""
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"registry": "https://registry.npmjs.org/"
}
},
"version": "16.1.1-canary.33"
"version": "16.1.1-canary.35"
}
32 changes: 16 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
"eslint-plugin-jsdoc": "48.0.4",
"eslint-plugin-mdx": "3.1.5",
"eslint-plugin-react": "7.37.0",
"eslint-plugin-react-hooks": "0.0.0-experimental-bef88f7c-20260116",
"eslint-plugin-react-hooks": "0.0.0-experimental-d2908752-20260119",
"event-stream": "4.0.1",
"execa": "2.0.3",
"expect": "29.7.0",
Expand Down Expand Up @@ -258,16 +258,16 @@
"pretty-ms": "7.0.0",
"random-seed": "0.3.0",
"react": "19.0.0",
"react-builtin": "npm:react@19.3.0-canary-bef88f7c-20260116",
"react-builtin": "npm:react@19.3.0-canary-d2908752-20260119",
"react-dom": "19.0.0",
"react-dom-builtin": "npm:react-dom@19.3.0-canary-bef88f7c-20260116",
"react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-bef88f7c-20260116",
"react-experimental-builtin": "npm:react@0.0.0-experimental-bef88f7c-20260116",
"react-is-builtin": "npm:react-is@19.3.0-canary-bef88f7c-20260116",
"react-server-dom-turbopack": "19.3.0-canary-bef88f7c-20260116",
"react-server-dom-turbopack-experimental": "npm:react-server-dom-turbopack@0.0.0-experimental-bef88f7c-20260116",
"react-server-dom-webpack": "19.3.0-canary-bef88f7c-20260116",
"react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-bef88f7c-20260116",
"react-dom-builtin": "npm:react-dom@19.3.0-canary-d2908752-20260119",
"react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-d2908752-20260119",
"react-experimental-builtin": "npm:react@0.0.0-experimental-d2908752-20260119",
"react-is-builtin": "npm:react-is@19.3.0-canary-d2908752-20260119",
"react-server-dom-turbopack": "19.3.0-canary-d2908752-20260119",
"react-server-dom-turbopack-experimental": "npm:react-server-dom-turbopack@0.0.0-experimental-d2908752-20260119",
"react-server-dom-webpack": "19.3.0-canary-d2908752-20260119",
"react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-d2908752-20260119",
"react-ssr-prepass": "1.0.8",
"react-virtualized": "9.22.3",
"relay-compiler": "13.0.2",
Expand All @@ -277,8 +277,8 @@
"resolve-from": "5.0.0",
"sass": "1.54.0",
"satori": "0.15.2",
"scheduler-builtin": "npm:scheduler@0.28.0-canary-bef88f7c-20260116",
"scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-bef88f7c-20260116",
"scheduler-builtin": "npm:scheduler@0.28.0-canary-d2908752-20260119",
"scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-d2908752-20260119",
"seedrandom": "3.0.5",
"semver": "7.3.7",
"serve-handler": "6.1.6",
Expand Down Expand Up @@ -323,10 +323,10 @@
"@types/react-dom": "19.2.1",
"@types/retry": "0.12.0",
"jest-snapshot": "30.0.0-alpha.6",
"react": "19.3.0-canary-bef88f7c-20260116",
"react-dom": "19.3.0-canary-bef88f7c-20260116",
"react-is": "19.3.0-canary-bef88f7c-20260116",
"scheduler": "0.28.0-canary-bef88f7c-20260116"
"react": "19.3.0-canary-d2908752-20260119",
"react-dom": "19.3.0-canary-d2908752-20260119",
"react-is": "19.3.0-canary-d2908752-20260119",
"scheduler": "0.28.0-canary-d2908752-20260119"
},
"packageExtensions": {
"eslint-plugin-react-hooks@0.0.0-experimental-6de32a5a-20250822": {
Expand Down
2 changes: 1 addition & 1 deletion packages/create-next-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"keywords": [
"react",
"next",
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-config-next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"description": "ESLint configuration used by Next.js.",
"license": "MIT",
"repository": {
Expand All @@ -12,7 +12,7 @@
"dist"
],
"dependencies": {
"@next/eslint-plugin-next": "16.1.1-canary.33",
"@next/eslint-plugin-next": "16.1.1-canary.35",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-internal/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@next/eslint-plugin-internal",
"private": true,
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"description": "ESLint plugin for working on Next.js.",
"exports": {
".": "./src/eslint-plugin-internal.js"
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"description": "ESLint plugin for Next.js.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/font/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@next/font",
"private": true,
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"repository": {
"url": "vercel/next.js",
"directory": "packages/font"
Expand Down
2 changes: 1 addition & 1 deletion packages/next-bundle-analyzer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"main": "index.js",
"types": "index.d.ts",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-codemod/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-env/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/env",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"keywords": [
"react",
"next",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-mdx/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"main": "index.js",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/next-plugin-storybook/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
Expand Down
2 changes: 1 addition & 1 deletion packages/next-polyfill-module/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-module",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)",
"main": "dist/polyfill-module.js",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-polyfill-nomodule/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-routing/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/routing",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"keywords": [
"react",
"next",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-rspack/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "next-rspack",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-rspack"
Expand Down
2 changes: 1 addition & 1 deletion packages/next-swc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/swc",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"private": true,
"files": [
"native/"
Expand Down
14 changes: 7 additions & 7 deletions packages/next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "next",
"version": "16.1.1-canary.33",
"version": "16.1.1-canary.35",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
Expand Down Expand Up @@ -97,7 +97,7 @@
]
},
"dependencies": {
"@next/env": "16.1.1-canary.33",
"@next/env": "16.1.1-canary.35",
"@swc/helpers": "0.5.15",
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001579",
Expand Down Expand Up @@ -162,11 +162,11 @@
"@modelcontextprotocol/sdk": "1.18.1",
"@mswjs/interceptors": "0.23.0",
"@napi-rs/triples": "1.2.0",
"@next/font": "16.1.1-canary.33",
"@next/polyfill-module": "16.1.1-canary.33",
"@next/polyfill-nomodule": "16.1.1-canary.33",
"@next/react-refresh-utils": "16.1.1-canary.33",
"@next/swc": "16.1.1-canary.33",
"@next/font": "16.1.1-canary.35",
"@next/polyfill-module": "16.1.1-canary.35",
"@next/polyfill-nomodule": "16.1.1-canary.35",
"@next/react-refresh-utils": "16.1.1-canary.35",
"@next/swc": "16.1.1-canary.35",
"@opentelemetry/api": "1.6.0",
"@playwright/test": "1.51.1",
"@rspack/core": "1.6.7",
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/build/define-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ export function getDefineEnv({
config.experimental.reactDebugChannel ?? false,
'process.env.__NEXT_TRANSITION_INDICATOR':
config.experimental.transitionIndicator ?? false,
'process.env.__NEXT_GESTURE_TRANSITION':
config.experimental.gestureTransition ?? false,
'process.env.__NEXT_CACHE_LIFE': config.cacheLife,
'process.env.__NEXT_CLIENT_PARAM_PARSING_ORIGINS':
config.experimental.clientParamParsingOrigins || [],
Expand Down
65 changes: 64 additions & 1 deletion packages/next/src/client/components/app-router-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ import {
type PrefetchTaskFetchStrategy,
} from './segment-cache/types'
import { prefetch as prefetchWithSegmentCache } from './segment-cache/prefetch'
import { dispatchAppRouterAction } from './use-action-queue'
import { navigate } from './segment-cache/navigation'
import {
dispatchAppRouterAction,
dispatchGestureState,
} from './use-action-queue'
import { FreshnessPolicy } from './router-reducer/ppr-navigations'
import { addBasePath } from '../add-base-path'
import { isExternalURL } from './app-router-utils'
import type {
Expand Down Expand Up @@ -313,6 +318,59 @@ export function dispatchTraverseAction(
})
}

/**
* (Experimental) Perform a gesture navigation. This dispatches through React's
* useOptimistic instead of the main action queue, allowing the state to be
* shown during a gesture transition and discarded when the canonical navigation
* completes.
*
* Only available when experimental.gestureTransition is enabled.
*/
function gesturePush(href: string, options?: NavigateOptions): void {
if (process.env.__NEXT_GESTURE_TRANSITION) {
// TODO: Trigger a prefetch so the cache starts populating if there isn't
// already a prefetch for this route.
if (isJavaScriptURLString(href)) {
throw new Error(
'Next.js has blocked a javascript: URL as a security precaution.'
)
}

const state = getCurrentAppRouterState()
if (state === null) {
return
}
const url = new URL(addBasePath(href), location.href)
if (isExternalURL(url)) {
return
}

// Fork the router state for the duration of the gesture transition.
const currentUrl = new URL(state.canonicalUrl, location.href)
const shouldScroll = options?.scroll ?? true
// This is a special freshness policy that prevents dynamic requests from
// being spawned. During the gesture, we should only show the cached
// prefetched UI, not dynamic data.
// TODO: In the case of navigations to an unknown route, this will still
// end up performing a dynamic request. The plan is to do prefetch instead.
// There's a separate TODO for this.
const freshnessPolicy = FreshnessPolicy.Gesture
const forkedGestureState = navigate(
state,
url,
currentUrl,
state.renderedSearch,
state.cache,
state.tree,
state.nextUrl,
freshnessPolicy,
shouldScroll,
'push'
)
dispatchGestureState(forkedGestureState)
}
}

/**
* The app router that is exposed through `useRouter`. These are public API
* methods. Internal Next.js code should call the lower level methods directly
Expand Down Expand Up @@ -407,6 +465,11 @@ export const publicAppRouterInstance: AppRouterInstance = {
},
}

// Conditionally add experimental_gesturePush when gestureTransition is enabled
if (process.env.__NEXT_GESTURE_TRANSITION) {
;(publicAppRouterInstance as any).experimental_gesturePush = gesturePush
}

// Exists for debugging purposes. Don't use in application code.
if (typeof window !== 'undefined' && window.next) {
window.next.router = publicAppRouterInstance
Expand Down
Loading
Loading