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,46 @@
'use client'
import { useSyncExternalStore } from 'react'
const reactServerRequestsServerSnapshot: never[] = []
const ServerRequestsStore = {
subscribe:
typeof window === 'undefined'
? () => () => {}
: window.reactServerRequests.subscribe,
getSnapshot:
typeof window === 'undefined'
? () => []
: window.reactServerRequests.getStoreSnapshot,
getServerSnapshot: () => reactServerRequestsServerSnapshot,
}
export function ReactServerRequests() {
const reactServerRequests = useSyncExternalStore(
ServerRequestsStore.subscribe,
ServerRequestsStore.getSnapshot,
ServerRequestsStore.getServerSnapshot
)
return (
<>
<p>{reactServerRequests.length} Server Request entries</p>
<ol>
{reactServerRequests.map((request, index) => (
<li key={index}>
<details>
<summary>
<strong>
{request.type}: {request.name || '<Unnamed>'}
</strong>
({Math.round(request.startTime)}ms
{Math.round(request.endTime)}ms)
</summary>
<pre>{JSON.stringify(request.properties, null, 2)}</pre>
</details>
</li>
))}
</ol>
</>
)
}

View File

@@ -0,0 +1,7 @@
import { cookies } from 'next/headers'
export default async function CookiesPage() {
await cookies()
return <p>Done cookies</p>
}

View File

@@ -0,0 +1,7 @@
import { draftMode } from 'next/headers'
export default async function DraftModePage() {
await draftMode()
return <p>Done draftMode</p>
}

View File

@@ -0,0 +1,10 @@
async function abstraction() {
await fetch('https://next-data-api-endpoint.vercel.app/api/random')
}
export default async function FetchPage() {
await abstraction()
await fetch('https://next-data-api-endpoint.vercel.app/api/random')
return <p>Done</p>
}

View File

@@ -0,0 +1,7 @@
import { headers } from 'next/headers'
export default async function HeadersPage() {
await headers()
return <p>Done headers</p>
}

View File

@@ -0,0 +1,45 @@
import { ReactNode, Suspense } from 'react'
import { ReactServerRequests } from './ReactServerRequests'
import Link from 'next/link'
export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body style={{ display: 'flex', flexDirection: 'row', gap: '1rem' }}>
<ul>
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/fetch">fetch</Link>
</li>
<li>
<Link href="/set-timeout">setTimeout</Link>
</li>
<li>
<Link href="/params/next">params</Link>
</li>
<li>
<Link href="/searchparams?slug=next">searchParams</Link>
</li>
<li>
<Link href="/headers">headers</Link>
</li>
<li>
<Link href="/cookies">cookies</Link>
</li>
<li>
<Link href="/draftMode">draftMode</Link>
</li>
</ul>
<main>
<Suspense fallback="Loading Server Requests">
<div data-react-server-requests-done />
{children}
</Suspense>
<ReactServerRequests />
</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <p>Hello, World!</p>
}

View File

@@ -0,0 +1,9 @@
export default async function ParamsSlugPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <p>Done {slug}</p>
}

View File

@@ -0,0 +1,7 @@
export default async function SearchParamsPage({
searchParams,
}: {
searchParams: Promise<Record<string, unknown>>
}) {
return <p>Done {JSON.stringify(await searchParams)}</p>
}

View File

@@ -0,0 +1,12 @@
import { setTimeout } from 'timers/promises'
async function abstraction(timeoutMS: number) {
await setTimeout(timeoutMS)
}
export default async function SetTimeoutPage() {
await abstraction(5)
await setTimeout(1000)
return <p>Done</p>
}

View File

@@ -0,0 +1,105 @@
export {}
type ReactServerRequests = Array<{
type: 'performance' | 'console'
name: string
properties: any
startTime: number
endTime: number
}>
declare global {
interface Window {
reactServerRequests: {
/** For tests */
getSnapshot: () => Array<
Omit<ReactServerRequests[number], 'startTime' | 'endTime' | 'type'>
>
getStoreSnapshot(): ReactServerRequests
subscribe(callback: () => void): () => void
}
}
interface PerformanceEntry {
detail?: any
}
}
let reactServerRequests: ReactServerRequests = []
const listeners = new Set<() => void>()
// The store implementation is just a local debugging aid.
// Assertions should happen on `getSnapshot` not on the UI.
window.reactServerRequests = {
getSnapshot: () => {
return reactServerRequests
.filter((request) => {
const isRegisterTrackRequest =
request.type === 'console' &&
request.name === undefined &&
request.startTime === 0.001 &&
request.endTime === 0.001
return !isRegisterTrackRequest
})
.map(({ startTime, endTime, type, ...rest }) => rest)
},
getStoreSnapshot: () => {
return reactServerRequests
},
subscribe: (callback) => {
listeners.add(callback)
return () => {
listeners.delete(callback)
}
},
}
const originalConsoleTimeStamp = console.timeStamp
console.timeStamp = (...args: any) => {
originalConsoleTimeStamp.apply(console, args)
const [name, startTime, endTime, track] = args
if (track === 'Server Requests ⚛') {
const isRegisterTrackRequest = startTime === 0.001 && endTime === 0.001
if (!isRegisterTrackRequest) {
reactServerRequests.push({
type: 'console',
name: name ?? '',
properties: [],
startTime,
endTime,
})
for (const listener of listeners) {
listener()
}
}
}
}
// We're trying to mock how the Chrome DevTools performance panel will display
// React performance data. React might decide to use console.timeStamp instead
// or any other method that will be picked up by the performance panel so this
// logic may have to be adjusted when updating React. A change here, doesn't
// mean it's a breaking change in React nor Next.js.
new PerformanceObserver((entries) => {
const newRequests: ReactServerRequests = []
for (const entry of entries.getEntries()) {
if (entry.detail?.devtools?.track === 'Server Requests ⚛') {
newRequests.push({
type: 'performance',
name: entry.name,
properties: entry.detail.devtools.properties,
startTime: entry.startTime,
endTime: entry.startTime + entry.duration,
})
}
}
if (newRequests.length > 0) {
reactServerRequests = reactServerRequests.concat(newRequests)
for (const listener of listeners) {
listener()
}
}
}).observe({ entryTypes: ['measure'] })

View File

@@ -0,0 +1,12 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
cacheComponents: true,
experimental: {
// Future but produces bad results.
reactDebugChannel: false,
},
}
module.exports = nextConfig

View File

@@ -0,0 +1,151 @@
import { nextTestSetup } from 'e2e-utils'
describe('react-performance-track', () => {
// false is the default when visiting pages as an ordinary user.
// true is the default when having Chrome DevTools open.
// Hardcoded for now since most of the actual behavior is not intended.
const disableCache = false
const extraHTTPHeaders = disableCache
? { 'Cache-Control': 'no-cache' }
: undefined
const { next } = nextTestSetup({
files: __dirname,
})
it('should show setTimeout', async () => {
const browser = await next.browser('/set-timeout', { extraHTTPHeaders })
await browser.elementByCss('[data-react-server-requests-done]', {
state: 'attached',
})
const track = await browser.eval('window.reactServerRequests.getSnapshot()')
expect(track).toEqual(
expect.arrayContaining([
{ name: '\u200bsetTimeout', properties: [] },
{ name: '\u200bsetTimeout', properties: [] },
])
)
})
it('should show fetch', async () => {
const browser = await next.browser('/fetch', { extraHTTPHeaders })
await browser.elementByCss('[data-react-server-requests-done]', {
state: 'attached',
})
const track = await browser.eval('window.reactServerRequests.getSnapshot()')
expect(track).toEqual(
expect.arrayContaining([
{
// React might decide to display the shorthand in round brackets differently.
// Double check with React changes if a shorthand change is intended.
name: '\u200bfetch (…/random)',
properties: expect.arrayContaining([
['status', '200'],
['url', '"https://next-data-api-endpoint.vercel.app/api/random"'],
]),
},
])
)
})
it('should show params', async () => {
const browser = await next.browser('/params/next', { extraHTTPHeaders })
await browser.elementByCss('[data-react-server-requests-done]', {
state: 'attached',
})
const track = await browser.eval('window.reactServerRequests.getSnapshot()')
expect(track).toEqual(
expect.arrayContaining([
{
name: '\u200bparams [Prefetchable]',
properties: [],
},
])
)
})
it('should show searchParams', async () => {
const browser = await next.browser('/searchparams?slug=next', {
extraHTTPHeaders,
})
await browser.elementByCss('[data-react-server-requests-done]', {
state: 'attached',
})
const track = await browser.eval('window.reactServerRequests.getSnapshot()')
expect(track).toEqual(
expect.arrayContaining([
{
name: '\u200bsearchParams [Prefetchable]',
properties: [],
},
])
)
})
it('should show cookies', async () => {
const browser = await next.browser('/cookies', { extraHTTPHeaders })
await browser.elementByCss('[data-react-server-requests-done]', {
state: 'attached',
})
const track = await browser.eval('window.reactServerRequests.getSnapshot()')
expect(track).toEqual(
expect.arrayContaining([
{
name: '\u200bcookies [Prefetchable]',
properties: [],
},
])
)
})
it('should show draftMode', async () => {
const browser = await next.browser('/draftMode', { extraHTTPHeaders })
await browser.elementByCss('[data-react-server-requests-done]', {
state: 'attached',
})
const track = await browser.eval('window.reactServerRequests.getSnapshot()')
// TODO the addition of a promise to delay some Segments from rendering until the later Static
// stage has caused the draftMode snapshot to include an empty-named entry. This is probably
// a bug in React and should be fixed there but
// expect(track).toEqual([])
expect(track).toEqual(
expect.arrayContaining([
{
name: '\u200b [Prerender]',
properties: [],
},
])
)
let didThrow = false
try {
// including this anti-assertion here so we can restore the test when the bug in React is fixed
expect(track).toEqual([])
} catch (e) {
didThrow = true
}
expect(didThrow).toBe(true)
})
it('should show headers', async () => {
const browser = await next.browser('/headers', { extraHTTPHeaders })
await browser.elementByCss('[data-react-server-requests-done]', {
state: 'attached',
})
const track = await browser.eval('window.reactServerRequests.getSnapshot()')
expect(track).toEqual(
expect.arrayContaining([
{
name: '\u200bheaders [Prefetchable]',
properties: [],
},
])
)
})
})