first commit
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled

This commit is contained in:
Arian Tron
2026-03-10 19:37:31 +03:30
commit 61f56f997c
27684 changed files with 2784175 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
import { ReactNode } from 'react'
export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body
style={{
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
maxWidth: 640,
margin: '0 auto',
padding: '2rem 1rem',
lineHeight: 1.6,
color: '#111',
}}
>
{children}
</body>
</html>
)
}

View File

@@ -0,0 +1,49 @@
import Link from 'next/link'
export default function Page() {
return (
<div>
<h1 data-testid="home-title">Instant Navigation Mode Demo</h1>
<p>
This fixture tests the <strong>Instant Navigation Mode</strong> toggle
in Next.js Dev Tools. When enabled, navigations show only the cached or
prefetched state dynamic data is not streamed.
</p>
<h2>How to test</h2>
<ol>
<li>
Open <strong>Next.js Dev Tools</strong> (click the Next.js logo in the
corner).
</li>
<li>
Toggle <strong>Instant Navigation Mode</strong> to <em>On</em>. The
indicator turns blue.
</li>
<li>
Click the link below. You should see the loading skeleton instead of
the final page content.
</li>
<li>
Click the blue <em>Instant UI only</em> indicator to unblock dynamic
data and resume normal navigation.
</li>
</ol>
<nav style={{ marginTop: '1.5rem' }}>
<Link
href="/target-page"
id="link-to-target"
style={{
display: 'inline-block',
padding: '0.5rem 1rem',
background: '#0070f3',
color: '#fff',
borderRadius: 6,
textDecoration: 'none',
}}
>
Go to target page &rarr;
</Link>
</nav>
</div>
)
}

View File

@@ -0,0 +1,54 @@
export default function Loading() {
return (
<div data-testid="loading-shell">
<style>{`
@keyframes shimmer {
0% { background-position: -400px 0; }
100% { background-position: 400px 0; }
}
.skeleton {
background: linear-gradient(90deg, #eee 25%, #ddd 50%, #eee 75%);
background-size: 800px 100%;
animation: shimmer 1.5s ease-in-out infinite;
border-radius: 4px;
}
`}</style>
<div
className="skeleton"
style={{ width: 160, height: 28, marginBottom: 12 }}
/>
<div
className="skeleton"
style={{ width: '100%', height: 14, marginBottom: 8 }}
/>
<div
className="skeleton"
style={{ width: '80%', height: 14, marginBottom: 24 }}
/>
<div
className="skeleton"
style={{ width: 100, height: 20, marginBottom: 16 }}
/>
<div
style={{
border: '1px solid #eee',
borderRadius: 8,
overflow: 'hidden',
}}
>
{[0, 1, 2].map((i) => (
<div
key={i}
style={{ padding: '0.75rem', borderBottom: '1px solid #eee' }}
>
<div className="skeleton" style={{ width: 80, height: 14 }} />
<div
className="skeleton"
style={{ width: '90%', height: 14, marginTop: 8 }}
/>
</div>
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,126 @@
import { Suspense } from 'react'
import { connection } from 'next/server'
import Link from 'next/link'
function Skeleton({
width,
height,
style,
}: {
width: string | number
height: number
style?: React.CSSProperties
}) {
return (
<div
className="skeleton"
style={{
width,
height,
borderRadius: 4,
background: 'linear-gradient(90deg, #eee 25%, #ddd 50%, #eee 75%)',
backgroundSize: '800px 100%',
animation: 'shimmer 1.5s ease-in-out infinite',
...style,
}}
/>
)
}
async function DynamicComments() {
await connection()
const comments = [
{ author: 'Alice', text: 'This loaded dynamically via streaming.' },
{
author: 'Bob',
text: 'With Instant Navigation Mode on, you would see the skeleton instead.',
},
{
author: 'Charlie',
text: 'Click the "Instant UI only" indicator to unblock dynamic data.',
},
]
return (
<div data-testid="dynamic-content">
{comments.map((c, i) => (
<div
key={i}
style={{
padding: '0.75rem',
borderBottom: '1px solid #eee',
}}
>
<strong>{c.author}</strong>
<p style={{ margin: '0.25rem 0 0' }}>{c.text}</p>
</div>
))}
</div>
)
}
function CommentsSkeleton() {
return (
<div data-testid="comments-skeleton">
{[0, 1, 2].map((i) => (
<div
key={i}
style={{
padding: '0.75rem',
borderBottom: '1px solid #eee',
}}
>
<Skeleton width={80} height={14} />
<Skeleton width="90%" height={14} style={{ marginTop: 8 }} />
</div>
))}
</div>
)
}
export default function TargetPage() {
return (
<div>
<style>{`
@keyframes shimmer {
0% { background-position: -400px 0; }
100% { background-position: 400px 0; }
}
`}</style>
<h1>Target Page</h1>
<p>
The heading and this paragraph are static they appear instantly. The
comments below are dynamic and stream in after the shell.
</p>
<h2 style={{ fontSize: '1.1rem', marginTop: '1.5rem' }}>Comments</h2>
<div
style={{
border: '1px solid #eee',
borderRadius: 8,
overflow: 'hidden',
marginBottom: '1.5rem',
}}
>
<Suspense fallback={<CommentsSkeleton />}>
<DynamicComments />
</Suspense>
</div>
<nav>
<Link
href="/"
style={{
display: 'inline-block',
padding: '0.5rem 1rem',
background: '#0070f3',
color: '#fff',
borderRadius: 6,
textDecoration: 'none',
}}
>
&larr; Back to home
</Link>
</nav>
</div>
)
}

View File

@@ -0,0 +1,188 @@
import { nextTestSetup } from 'e2e-utils'
import {
retry,
waitForDevToolsIndicator,
toggleDevToolsIndicatorPopover,
} from 'next-test-utils'
describe('instant-mode-toggle', () => {
const { next } = nextTestSetup({
files: __dirname,
})
async function clearInstantModeCookie(browser: any) {
await browser.eval(() => {
document.cookie = 'next-instant-navigation-testing=; path=/; max-age=0'
})
}
async function clickInstantModeMenuItem(browser: any) {
await browser.eval(() => {
const portal = [].slice
.call(document.querySelectorAll('nextjs-portal'))
.find((p: any) =>
p.shadowRoot.querySelector('[data-nextjs-toast]')
) as any
portal?.shadowRoot?.querySelector('[data-cache-only]')?.click()
})
}
async function getInstantModeMenuValue(browser: any): Promise<string> {
return browser.eval(() => {
const portal = [].slice
.call(document.querySelectorAll('nextjs-portal'))
.find((p: any) =>
p.shadowRoot.querySelector('[data-nextjs-toast]')
) as any
return (
portal?.shadowRoot
?.querySelector('[data-cache-only]')
?.innerText.split('\n')
.pop() || ''
)
})
}
async function getBadgeStatus(browser: any): Promise<string> {
return browser.eval(() => {
const portal = [].slice
.call(document.querySelectorAll('nextjs-portal'))
.find((p: any) =>
p.shadowRoot.querySelector('[data-nextjs-toast]')
) as any
return (
portal?.shadowRoot
?.querySelector('[data-next-badge]')
?.getAttribute('data-status') || ''
)
})
}
it('should show "instant" status after toggling on, not stuck on "compiling"', async () => {
const browser = await next.browser('/')
await clearInstantModeCookie(browser)
await browser.waitForElementByCss('[data-testid="home-title"]')
// Wait for initial compilation to settle — the badge status should
// become "none" once compilation is done and no transient status is active
await retry(async () => {
const status = await getBadgeStatus(browser)
expect(status).toBe('none')
})
// Toggle instant mode on
await waitForDevToolsIndicator(browser)
await toggleDevToolsIndicatorPopover(browser)
await clickInstantModeMenuItem(browser)
// The badge status should settle to "instant", not stay on "compiling"
await retry(async () => {
const status = await getBadgeStatus(browser)
expect(status).toBe('instant')
})
// Clean up
await clearInstantModeCookie(browser)
})
it('should show "Instant mode" menu item and toggle it on', async () => {
const browser = await next.browser('/')
await clearInstantModeCookie(browser)
await waitForDevToolsIndicator(browser)
await toggleDevToolsIndicatorPopover(browser)
// Verify the "Instant mode" menu item shows "Off"
await retry(async () => {
const value = await getInstantModeMenuValue(browser)
expect(value).toBe('Off')
})
// Click to toggle on — this also closes the menu
await clickInstantModeMenuItem(browser)
// Verify the badge appears with data-cache-only="true"
await retry(async () => {
const badge = await browser.elementByCss('[data-next-badge]')
const attr = await badge.getAttribute('data-cache-only')
expect(attr).toBe('true')
})
// Verify the status indicator shows "Instant..."
await retry(async () => {
const hasIndicator = await browser.hasElementByCss(
'[data-indicator-status]'
)
expect(hasIndicator).toBe(true)
})
// Clean up
await clearInstantModeCookie(browser)
})
it('should show loading skeleton during SPA navigation when instant mode is on', async () => {
const browser = await next.browser('/')
await clearInstantModeCookie(browser)
await browser.waitForElementByCss('[data-testid="home-title"]')
// Toggle instant mode on
await toggleDevToolsIndicatorPopover(browser)
await clickInstantModeMenuItem(browser)
// Wait for instant mode to be active
await retry(async () => {
const badge = await browser.elementByCss('[data-next-badge]')
const attr = await badge.getAttribute('data-cache-only')
expect(attr).toBe('true')
})
// Navigate to target page via SPA
await browser.elementByCss('#link-to-target').click()
// The comments skeleton should be visible (dynamic content is locked)
// while static content (heading, paragraph) is already rendered
await retry(async () => {
const skeleton = await browser.hasElementByCss(
'[data-testid="comments-skeleton"]'
)
expect(skeleton).toBe(true)
})
// Clean up
await clearInstantModeCookie(browser)
})
it('should turn off instant mode when clicking the badge', async () => {
const browser = await next.browser('/')
await clearInstantModeCookie(browser)
await browser.waitForElementByCss('[data-testid="home-title"]')
// Toggle instant mode on via menu
await toggleDevToolsIndicatorPopover(browser)
await clickInstantModeMenuItem(browser)
// Verify it's on
await retry(async () => {
const badge = await browser.elementByCss('[data-next-badge]')
const attr = await badge.getAttribute('data-cache-only')
expect(attr).toBe('true')
})
// Click the "Instant..." status indicator to unlock — this clears the cookie and reloads
await browser.eval(() => {
const portal = [].slice
.call(document.querySelectorAll('nextjs-portal'))
.find((p: any) =>
p.shadowRoot.querySelector('[data-nextjs-toast]')
) as any
portal?.shadowRoot?.querySelector('[data-indicator-status]')?.click()
})
// After reload, instant mode should be off
await retry(async () => {
const badge = await browser.elementByCss('[data-next-badge]')
const attr = await badge.getAttribute('data-cache-only')
expect(attr).toBe('false')
})
})
})

View File

@@ -0,0 +1,11 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
cacheComponents: true,
experimental: {
instantNavigationDevToolsToggle: true,
},
}
module.exports = nextConfig