diff --git a/errors/missing-suspense-with-csr-bailout.mdx b/errors/missing-suspense-with-csr-bailout.mdx deleted file mode 100644 index bf5aa02a01..0000000000 --- a/errors/missing-suspense-with-csr-bailout.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Missing Suspense with CSR Bailout ---- - -#### Why This Error Occurred - -Certain methods like `useSearchParams()` opt Next.js into client-side rendering. Without a suspense boundary, this will opt the entire page into client-side rendering, which is likely not intended. - -#### Possible Ways to Fix It - -Make sure that the method is wrapped in a suspense boundary. This way Next.js will only opt the component into client-side rendering up to the suspense boundary. - -### Useful Links - -- [`useSearchParams`](https://nextjs.org/docs/app/api-reference/functions/use-search-params) diff --git a/packages/next/src/client/components/bailout-to-client-rendering.ts b/packages/next/src/client/components/bailout-to-client-rendering.ts index cf2317d989..6af1fe9dab 100644 --- a/packages/next/src/client/components/bailout-to-client-rendering.ts +++ b/packages/next/src/client/components/bailout-to-client-rendering.ts @@ -1,11 +1,14 @@ -import { BailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr' +import { throwWithNoSSR } from '../../shared/lib/lazy-dynamic/no-ssr-error' import { staticGenerationAsyncStorage } from './static-generation-async-storage.external' -export function bailoutToClientRendering(reason: string): void | never { +export function bailoutToClientRendering(): void | never { const staticGenerationStore = staticGenerationAsyncStorage.getStore() - if (staticGenerationStore?.forceStatic) return + if (staticGenerationStore?.forceStatic) { + return + } - if (staticGenerationStore?.isStaticGeneration) - throw new BailoutToCSRError(reason) + if (staticGenerationStore?.isStaticGeneration) { + throwWithNoSSR() + } } diff --git a/packages/next/src/client/components/navigation.ts b/packages/next/src/client/components/navigation.ts index a391ef23b4..8290091e41 100644 --- a/packages/next/src/client/components/navigation.ts +++ b/packages/next/src/client/components/navigation.ts @@ -92,7 +92,7 @@ export function useSearchParams(): ReadonlyURLSearchParams { const { bailoutToClientRendering } = require('./bailout-to-client-rendering') as typeof import('./bailout-to-client-rendering') // TODO-APP: handle dynamic = 'force-static' here and on the client - bailoutToClientRendering('useSearchParams()') + bailoutToClientRendering() } return readonlySearchParams diff --git a/packages/next/src/client/on-recoverable-error.ts b/packages/next/src/client/on-recoverable-error.ts index 43ba74f341..c22e5eec89 100644 --- a/packages/next/src/client/on-recoverable-error.ts +++ b/packages/next/src/client/on-recoverable-error.ts @@ -1,6 +1,6 @@ -import { isBailoutToCSRError } from '../shared/lib/lazy-dynamic/bailout-to-csr' +import { isBailoutCSRError } from '../shared/lib/lazy-dynamic/no-ssr-error' -export default function onRecoverableError(err: unknown) { +export default function onRecoverableError(err: any) { // Using default react onRecoverableError // x-ref: https://github.com/facebook/react/blob/d4bc16a7d69eb2ea38a88c8ac0b461d5f72cdcab/packages/react-dom/src/client/ReactDOMRoot.js#L83 const defaultOnRecoverableError = @@ -13,7 +13,7 @@ export default function onRecoverableError(err: unknown) { } // Skip certain custom errors which are not expected to be reported on client - if (isBailoutToCSRError(err)) return + if (isBailoutCSRError(err)) return defaultOnRecoverableError(err) } diff --git a/packages/next/src/export/helpers/is-dynamic-usage-error.ts b/packages/next/src/export/helpers/is-dynamic-usage-error.ts index 0b5c535660..deede653d1 100644 --- a/packages/next/src/export/helpers/is-dynamic-usage-error.ts +++ b/packages/next/src/export/helpers/is-dynamic-usage-error.ts @@ -1,8 +1,10 @@ import { DYNAMIC_ERROR_CODE } from '../../client/components/hooks-server-context' import { isNotFoundError } from '../../client/components/not-found' import { isRedirectError } from '../../client/components/redirect' +import { isBailoutCSRError } from '../../shared/lib/lazy-dynamic/no-ssr-error' export const isDynamicUsageError = (err: any) => err.digest === DYNAMIC_ERROR_CODE || isNotFoundError(err) || + isBailoutCSRError(err) || isRedirectError(err) diff --git a/packages/next/src/export/index.ts b/packages/next/src/export/index.ts index e5ba675d59..d99993f196 100644 --- a/packages/next/src/export/index.ts +++ b/packages/next/src/export/index.ts @@ -506,11 +506,7 @@ export async function exportAppImpl( : {}), strictNextHead: !!nextConfig.experimental.strictNextHead, deploymentId: nextConfig.experimental.deploymentId, - experimental: { - ppr: nextConfig.experimental.ppr === true, - missingSuspenseWithCSRBailout: - nextConfig.experimental.missingSuspenseWithCSRBailout, - }, + experimental: { ppr: nextConfig.experimental.ppr === true }, } const { serverRuntimeConfig, publicRuntimeConfig } = nextConfig diff --git a/packages/next/src/export/routes/pages.ts b/packages/next/src/export/routes/pages.ts index 15329d564f..936b5159c9 100644 --- a/packages/next/src/export/routes/pages.ts +++ b/packages/next/src/export/routes/pages.ts @@ -15,7 +15,7 @@ import { NEXT_DATA_SUFFIX, SERVER_PROPS_EXPORT_ERROR, } from '../../lib/constants' -import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr' +import { isBailoutCSRError } from '../../shared/lib/lazy-dynamic/no-ssr-error' import AmpHtmlValidator from 'next/dist/compiled/amphtml-validator' import { FileType, fileExists } from '../../lib/file-exists' import { lazyRenderPagesPage } from '../../server/future/route-modules/pages/module.render' @@ -105,8 +105,10 @@ export async function exportPages( query, renderOpts ) - } catch (err) { - if (!isBailoutToCSRError(err)) throw err + } catch (err: any) { + if (!isBailoutCSRError(err)) { + throw err + } } } @@ -161,8 +163,10 @@ export async function exportPages( { ...query, amp: '1' }, renderOpts ) - } catch (err) { - if (!isBailoutToCSRError(err)) throw err + } catch (err: any) { + if (!isBailoutCSRError(err)) { + throw err + } } const ampHtml = diff --git a/packages/next/src/export/worker.ts b/packages/next/src/export/worker.ts index 7ba65044f7..5d2fd08578 100644 --- a/packages/next/src/export/worker.ts +++ b/packages/next/src/export/worker.ts @@ -35,7 +35,6 @@ import { createIncrementalCache } from './helpers/create-incremental-cache' import { isPostpone } from '../server/lib/router-utils/is-postpone' import { isMissingPostponeDataError } from '../server/app-render/is-missing-postpone-error' import { isDynamicUsageError } from './helpers/is-dynamic-usage-error' -import { isBailoutToCSRError } from '../shared/lib/lazy-dynamic/bailout-to-csr' const envConfig = require('../shared/lib/runtime-config.external') @@ -319,11 +318,9 @@ async function exportPageImpl( // if this is a postpone error, it's logged elsewhere, so no need to log it again here if (!isMissingPostponeDataError(err)) { console.error( - `\nError occurred prerendering page "${path}". Read more: https://nextjs.org/docs/messages/prerender-error\n` + `\nError occurred prerendering page "${path}". Read more: https://nextjs.org/docs/messages/prerender-error\n` + + (isError(err) && err.stack ? err.stack : err) ) - if (!isBailoutToCSRError(err)) { - console.error(isError(err) && err.stack ? err.stack : err) - } } return { error: true } diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 36e0dc72f4..5c62fecf83 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -63,7 +63,7 @@ import { parseAndValidateFlightRouterState } from './parse-and-validate-flight-r import { validateURL } from './validate-url' import { createFlightRouterStateFromLoaderTree } from './create-flight-router-state-from-loader-tree' import { handleAction } from './action-handler' -import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr' +import { isBailoutCSRError } from '../../shared/lib/lazy-dynamic/no-ssr-error' import { warn, error } from '../../build/output/log' import { appendMutableCookies } from '../web/spec-extension/adapters/request-cookies' import { createServerInsertedHTML } from './server-inserted-html' @@ -996,19 +996,12 @@ async function renderToHTMLOrFlightImpl( throw err } - /** True if this error was a bailout to client side rendering error. */ - const shouldBailoutToCSR = isBailoutToCSRError(err) + // True if this error was a bailout to client side rendering error. + const shouldBailoutToCSR = isBailoutCSRError(err) if (shouldBailoutToCSR) { - console.log() - - if (renderOpts.experimental.missingSuspenseWithCSRBailout) { - error( - `${err.message} should be wrapped in a suspense boundary at page "${pagePath}". https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout` - ) - throw err - } warn( - `Entire page "${pagePath}" deopted into client-side rendering. https://nextjs.org/docs/messages/deopted-into-client-rendering` + `Entire page ${pagePath} deopted into client-side rendering. https://nextjs.org/docs/messages/deopted-into-client-rendering`, + pagePath ) } @@ -1219,7 +1212,7 @@ async function renderToHTMLOrFlightImpl( renderOpts.experimental.ppr && staticGenerationStore.postponeWasTriggered && !metadata.postponed && - (!response.err || !isBailoutToCSRError(response.err)) + (!response.err || !isBailoutCSRError(response.err)) ) { // a call to postpone was made but was caught and not detected by Next.js. We should fail the build immediately // as we won't be able to generate the static part diff --git a/packages/next/src/server/app-render/create-error-handler.tsx b/packages/next/src/server/app-render/create-error-handler.tsx index 126823ce92..60ea9fac2d 100644 --- a/packages/next/src/server/app-render/create-error-handler.tsx +++ b/packages/next/src/server/app-render/create-error-handler.tsx @@ -3,7 +3,6 @@ import { formatServerError } from '../../lib/format-server-error' import { SpanStatusCode, getTracer } from '../lib/trace/tracer' import { isAbortError } from '../pipe-readable' import { isDynamicUsageError } from '../../export/helpers/is-dynamic-usage-error' -import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr' export type ErrorHandler = (err: any) => string | undefined @@ -35,12 +34,11 @@ export function createErrorHandler({ return (err) => { if (allCapturedErrors) allCapturedErrors.push(err) - // A formatted error is already logged for this type of error - if (isBailoutToCSRError(err)) return - // These errors are expected. We return the digest // so that they can be properly handled. - if (isDynamicUsageError(err)) return err.digest + if (isDynamicUsageError(err)) { + return err.digest + } // If the response was closed, we don't need to log the error. if (isAbortError(err)) return diff --git a/packages/next/src/server/app-render/types.ts b/packages/next/src/server/app-render/types.ts index a460d9d4f3..3cd3e1d2c8 100644 --- a/packages/next/src/server/app-render/types.ts +++ b/packages/next/src/server/app-render/types.ts @@ -142,7 +142,7 @@ export interface RenderOptsPartial { } params?: ParsedUrlQuery isPrefetch?: boolean - experimental: { ppr: boolean; missingSuspenseWithCSRBailout?: boolean } + experimental: { ppr: boolean } postponed?: string } diff --git a/packages/next/src/server/async-storage/static-generation-async-storage-wrapper.ts b/packages/next/src/server/async-storage/static-generation-async-storage-wrapper.ts index 70c25d5b8b..f3068d3f39 100644 --- a/packages/next/src/server/async-storage/static-generation-async-storage-wrapper.ts +++ b/packages/next/src/server/async-storage/static-generation-async-storage-wrapper.ts @@ -18,7 +18,7 @@ export type StaticGenerationContext = { isDraftMode?: boolean isServerAction?: boolean waitUntil?: Promise - experimental: { ppr: boolean; missingSuspenseWithCSRBailout?: boolean } + experimental: { ppr: boolean } /** * A hack around accessing the store value outside the context of the diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 33f642be5c..fd61fd9e60 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -371,7 +371,6 @@ export const configSchema: zod.ZodType = z.lazy(() => staticWorkerRequestDeduping: z.boolean().optional(), useWasmBinary: z.boolean().optional(), useLightningcss: z.boolean().optional(), - missingSuspenseWithCSRBailout: z.boolean().optional(), }) .optional(), exportPathMap: z diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index f86ccea112..b98bb57bac 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -380,16 +380,6 @@ export interface ExperimentalConfig { * Use lightningcss instead of swc_css */ useLightningcss?: boolean - - /** - * Certain methods calls like `useSearchParams()` can bail out of server-side rendering of **entire** pages to client-side rendering, - * if they are not wrapped in a suspense boundary. - * - * When this flag is set to `true`, Next.js will break the build instead of warning, to force the developer to add a suspense boundary above the method call. - * - * @default false - */ - missingSuspenseWithCSRBailout?: boolean } export type ExportPathMap = { @@ -852,7 +842,6 @@ export const defaultConfig: NextConfig = { ? true : false, webpackBuildWorker: undefined, - missingSuspenseWithCSRBailout: false, }, } diff --git a/packages/next/src/shared/lib/lazy-dynamic/bailout-to-csr.ts b/packages/next/src/shared/lib/lazy-dynamic/bailout-to-csr.ts deleted file mode 100644 index 24de76c203..0000000000 --- a/packages/next/src/shared/lib/lazy-dynamic/bailout-to-csr.ts +++ /dev/null @@ -1,14 +0,0 @@ -// This has to be a shared module which is shared between client component error boundary and dynamic component - -const BAILOUT_TO_CSR = 'BAILOUT_TO_CLIENT_SIDE_RENDERING' - -/** An error that should be thrown when we want to bail out to client-side rendering. */ -export class BailoutToCSRError extends Error { - digest: typeof BAILOUT_TO_CSR = BAILOUT_TO_CSR -} - -/** Checks if a passed argument is an error that is thrown if we want to bail out to client-side rendering. */ -export function isBailoutToCSRError(err: unknown): err is BailoutToCSRError { - if (typeof err !== 'object' || err === null) return false - return 'digest' in err && err.digest === BAILOUT_TO_CSR -} diff --git a/packages/next/src/shared/lib/lazy-dynamic/dynamic-bailout-to-csr.tsx b/packages/next/src/shared/lib/lazy-dynamic/dynamic-bailout-to-csr.tsx deleted file mode 100644 index dcb1ed1d8d..0000000000 --- a/packages/next/src/shared/lib/lazy-dynamic/dynamic-bailout-to-csr.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client' - -import type { ReactElement } from 'react' -import { BailoutToCSRError } from './bailout-to-csr' - -interface BailoutToCSRProps { - reason: string - children: ReactElement -} - -/** - * If rendered on the server, this component throws an error - * to signal Next.js that it should bail out to client-side rendering instead. - */ -export function BailoutToCSR({ reason, children }: BailoutToCSRProps) { - if (typeof window === 'undefined') { - throw new BailoutToCSRError(reason) - } - - return children -} diff --git a/packages/next/src/shared/lib/lazy-dynamic/dynamic-no-ssr.tsx b/packages/next/src/shared/lib/lazy-dynamic/dynamic-no-ssr.tsx new file mode 100644 index 0000000000..12795c79a6 --- /dev/null +++ b/packages/next/src/shared/lib/lazy-dynamic/dynamic-no-ssr.tsx @@ -0,0 +1,14 @@ +'use client' + +import type React from 'react' +import { throwWithNoSSR } from './no-ssr-error' + +type Child = React.ReactElement + +export function NoSSR({ children }: { children: Child }): Child { + if (typeof window === 'undefined') { + throwWithNoSSR() + } + + return children +} diff --git a/packages/next/src/shared/lib/lazy-dynamic/loadable.tsx b/packages/next/src/shared/lib/lazy-dynamic/loadable.tsx index 301eaeb2e1..d5dc700d8e 100644 --- a/packages/next/src/shared/lib/lazy-dynamic/loadable.tsx +++ b/packages/next/src/shared/lib/lazy-dynamic/loadable.tsx @@ -1,45 +1,43 @@ -import { Suspense, lazy } from 'react' -import { BailoutToCSR } from './dynamic-bailout-to-csr' +import { Suspense, lazy, Fragment } from 'react' +import { NoSSR } from './dynamic-no-ssr' import type { ComponentModule } from './types' // Normalize loader to return the module as form { default: Component } for `React.lazy`. // Also for backward compatible since next/dynamic allows to resolve a component directly with loader // Client component reference proxy need to be converted to a module. function convertModule

(mod: React.ComponentType

| ComponentModule

) { - return { default: (mod as ComponentModule

)?.default ?? mod } + return { default: (mod as ComponentModule

)?.default || mod } } -const defaultOptions = { - loader: () => Promise.resolve(convertModule(() => null)), - loading: null, - ssr: true, -} +function Loadable(options: any) { + const opts = { + loader: null, + loading: null, + ssr: true, + ...options, + } -interface LoadableOptions { - loader?: () => Promise | ComponentModule> - loading?: React.ComponentType | null - ssr?: boolean -} + const loader = () => + opts.loader != null + ? opts.loader().then(convertModule) + : Promise.resolve(convertModule(() => null)) -function Loadable(options: LoadableOptions) { - const opts = { ...defaultOptions, ...options } - const Lazy = lazy(() => opts.loader().then(convertModule)) + const Lazy = lazy(loader) const Loading = opts.loading + const Wrap = opts.ssr ? Fragment : NoSSR function LoadableComponent(props: any) { const fallbackElement = Loading ? ( ) : null - const children = opts.ssr ? ( - - ) : ( - - - + return ( + + + + + ) - - return {children} } LoadableComponent.displayName = 'LoadableComponent' diff --git a/packages/next/src/shared/lib/lazy-dynamic/no-ssr-error.ts b/packages/next/src/shared/lib/lazy-dynamic/no-ssr-error.ts new file mode 100644 index 0000000000..3bce562a5b --- /dev/null +++ b/packages/next/src/shared/lib/lazy-dynamic/no-ssr-error.ts @@ -0,0 +1,13 @@ +// This has to be a shared module which is shared between client component error boundary and dynamic component + +export const NEXT_DYNAMIC_NO_SSR_CODE = 'NEXT_DYNAMIC_NO_SSR_CODE' + +export function throwWithNoSSR() { + const error = new Error(NEXT_DYNAMIC_NO_SSR_CODE) + ;(error as any).digest = NEXT_DYNAMIC_NO_SSR_CODE + throw error +} + +export function isBailoutCSRError(err: any) { + return err?.digest === NEXT_DYNAMIC_NO_SSR_CODE +} diff --git a/test/e2e/app-dir/app-static/app-static.test.ts b/test/e2e/app-dir/app-static/app-static.test.ts index 314a2df2d2..a0d9119cb9 100644 --- a/test/e2e/app-dir/app-static/app-static.test.ts +++ b/test/e2e/app-dir/app-static/app-static.test.ts @@ -494,231 +494,235 @@ createNextDescribe( ) }) - expect(files.sort()).toMatchInlineSnapshot(` + expect(files.sort()).toEqual( [ - "(new)/custom/page.js", - "(new)/custom/page_client-reference-manifest.js", - "_not-found.html", - "_not-found.js", - "_not-found.rsc", - "_not-found_client-reference-manifest.js", - "api/draft-mode/route.js", - "api/large-data/route.js", - "api/revalidate-path-edge/route.js", - "api/revalidate-path-node/route.js", - "api/revalidate-tag-edge/route.js", - "api/revalidate-tag-node/route.js", - "articles/[slug]/page.js", - "articles/[slug]/page_client-reference-manifest.js", - "articles/works.html", - "articles/works.rsc", - "blog/[author]/[slug]/page.js", - "blog/[author]/[slug]/page_client-reference-manifest.js", - "blog/[author]/page.js", - "blog/[author]/page_client-reference-manifest.js", - "blog/seb.html", - "blog/seb.rsc", - "blog/seb/second-post.html", - "blog/seb/second-post.rsc", - "blog/styfle.html", - "blog/styfle.rsc", - "blog/styfle/first-post.html", - "blog/styfle/first-post.rsc", - "blog/styfle/second-post.html", - "blog/styfle/second-post.rsc", - "blog/tim.html", - "blog/tim.rsc", - "blog/tim/first-post.html", - "blog/tim/first-post.rsc", - "default-cache/page.js", - "default-cache/page_client-reference-manifest.js", - "dynamic-error/[id]/page.js", - "dynamic-error/[id]/page_client-reference-manifest.js", - "dynamic-no-gen-params-ssr/[slug]/page.js", - "dynamic-no-gen-params-ssr/[slug]/page_client-reference-manifest.js", - "dynamic-no-gen-params/[slug]/page.js", - "dynamic-no-gen-params/[slug]/page_client-reference-manifest.js", - "fetch-no-cache/page.js", - "fetch-no-cache/page_client-reference-manifest.js", - "flight/[slug]/[slug2]/page.js", - "flight/[slug]/[slug2]/page_client-reference-manifest.js", - "force-cache.html", - "force-cache.rsc", - "force-cache/large-data/page.js", - "force-cache/large-data/page_client-reference-manifest.js", - "force-cache/page.js", - "force-cache/page_client-reference-manifest.js", - "force-dynamic-catch-all/[slug]/[[...id]]/page.js", - "force-dynamic-catch-all/[slug]/[[...id]]/page_client-reference-manifest.js", - "force-dynamic-no-prerender/[id]/page.js", - "force-dynamic-no-prerender/[id]/page_client-reference-manifest.js", - "force-dynamic-prerender/[slug]/page.js", - "force-dynamic-prerender/[slug]/page_client-reference-manifest.js", - "force-no-store/page.js", - "force-no-store/page_client-reference-manifest.js", - "force-static-fetch-no-store.html", - "force-static-fetch-no-store.rsc", - "force-static-fetch-no-store/page.js", - "force-static-fetch-no-store/page_client-reference-manifest.js", - "force-static/[slug]/page.js", - "force-static/[slug]/page_client-reference-manifest.js", - "force-static/first.html", - "force-static/first.rsc", - "force-static/page.js", - "force-static/page_client-reference-manifest.js", - "force-static/second.html", - "force-static/second.rsc", - "gen-params-dynamic-revalidate/[slug]/page.js", - "gen-params-dynamic-revalidate/[slug]/page_client-reference-manifest.js", - "gen-params-dynamic-revalidate/one.html", - "gen-params-dynamic-revalidate/one.rsc", - "gen-params-dynamic/[slug]/page.js", - "gen-params-dynamic/[slug]/page_client-reference-manifest.js", - "hooks/use-pathname/[slug]/page.js", - "hooks/use-pathname/[slug]/page_client-reference-manifest.js", - "hooks/use-pathname/slug.html", - "hooks/use-pathname/slug.rsc", - "hooks/use-search-params/force-static.html", - "hooks/use-search-params/force-static.rsc", - "hooks/use-search-params/force-static/page.js", - "hooks/use-search-params/force-static/page_client-reference-manifest.js", - "hooks/use-search-params/with-suspense.html", - "hooks/use-search-params/with-suspense.rsc", - "hooks/use-search-params/with-suspense/page.js", - "hooks/use-search-params/with-suspense/page_client-reference-manifest.js", - "index.html", - "index.rsc", - "isr-error-handling.html", - "isr-error-handling.rsc", - "isr-error-handling/page.js", - "isr-error-handling/page_client-reference-manifest.js", - "no-store/dynamic/page.js", - "no-store/dynamic/page_client-reference-manifest.js", - "no-store/static.html", - "no-store/static.rsc", - "no-store/static/page.js", - "no-store/static/page_client-reference-manifest.js", - "page.js", - "page_client-reference-manifest.js", - "partial-gen-params-no-additional-lang/[lang]/[slug]/page.js", - "partial-gen-params-no-additional-lang/[lang]/[slug]/page_client-reference-manifest.js", - "partial-gen-params-no-additional-lang/en/RAND.html", - "partial-gen-params-no-additional-lang/en/RAND.rsc", - "partial-gen-params-no-additional-lang/en/first.html", - "partial-gen-params-no-additional-lang/en/first.rsc", - "partial-gen-params-no-additional-lang/en/second.html", - "partial-gen-params-no-additional-lang/en/second.rsc", - "partial-gen-params-no-additional-lang/fr/RAND.html", - "partial-gen-params-no-additional-lang/fr/RAND.rsc", - "partial-gen-params-no-additional-lang/fr/first.html", - "partial-gen-params-no-additional-lang/fr/first.rsc", - "partial-gen-params-no-additional-lang/fr/second.html", - "partial-gen-params-no-additional-lang/fr/second.rsc", - "partial-gen-params-no-additional-slug/[lang]/[slug]/page.js", - "partial-gen-params-no-additional-slug/[lang]/[slug]/page_client-reference-manifest.js", - "partial-gen-params-no-additional-slug/en/RAND.html", - "partial-gen-params-no-additional-slug/en/RAND.rsc", - "partial-gen-params-no-additional-slug/en/first.html", - "partial-gen-params-no-additional-slug/en/first.rsc", - "partial-gen-params-no-additional-slug/en/second.html", - "partial-gen-params-no-additional-slug/en/second.rsc", - "partial-gen-params-no-additional-slug/fr/RAND.html", - "partial-gen-params-no-additional-slug/fr/RAND.rsc", - "partial-gen-params-no-additional-slug/fr/first.html", - "partial-gen-params-no-additional-slug/fr/first.rsc", - "partial-gen-params-no-additional-slug/fr/second.html", - "partial-gen-params-no-additional-slug/fr/second.rsc", - "partial-gen-params/[lang]/[slug]/page.js", - "partial-gen-params/[lang]/[slug]/page_client-reference-manifest.js", - "react-fetch-deduping-edge/page.js", - "react-fetch-deduping-edge/page_client-reference-manifest.js", - "react-fetch-deduping-node/page.js", - "react-fetch-deduping-node/page_client-reference-manifest.js", - "response-url/page.js", - "response-url/page_client-reference-manifest.js", - "route-handler-edge/revalidate-360/route.js", - "route-handler/post/route.js", - "route-handler/revalidate-360-isr/route.js", - "route-handler/revalidate-360/route.js", - "route-handler/static-cookies/route.js", - "ssg-draft-mode.html", - "ssg-draft-mode.rsc", - "ssg-draft-mode/[[...route]]/page.js", - "ssg-draft-mode/[[...route]]/page_client-reference-manifest.js", - "ssg-draft-mode/test-2.html", - "ssg-draft-mode/test-2.rsc", - "ssg-draft-mode/test.html", - "ssg-draft-mode/test.rsc", - "ssr-auto/cache-no-store/page.js", - "ssr-auto/cache-no-store/page_client-reference-manifest.js", - "ssr-auto/fetch-revalidate-zero/page.js", - "ssr-auto/fetch-revalidate-zero/page_client-reference-manifest.js", - "ssr-forced/page.js", - "ssr-forced/page_client-reference-manifest.js", - "stale-cache-serving-edge/app-page/page.js", - "stale-cache-serving-edge/app-page/page_client-reference-manifest.js", - "stale-cache-serving-edge/route-handler/route.js", - "stale-cache-serving/app-page/page.js", - "stale-cache-serving/app-page/page_client-reference-manifest.js", - "stale-cache-serving/route-handler/route.js", - "static-to-dynamic-error-forced/[id]/page.js", - "static-to-dynamic-error-forced/[id]/page_client-reference-manifest.js", - "static-to-dynamic-error/[id]/page.js", - "static-to-dynamic-error/[id]/page_client-reference-manifest.js", - "variable-config-revalidate/revalidate-3.html", - "variable-config-revalidate/revalidate-3.rsc", - "variable-config-revalidate/revalidate-3/page.js", - "variable-config-revalidate/revalidate-3/page_client-reference-manifest.js", - "variable-revalidate-edge/body/page.js", - "variable-revalidate-edge/body/page_client-reference-manifest.js", - "variable-revalidate-edge/encoding/page.js", - "variable-revalidate-edge/encoding/page_client-reference-manifest.js", - "variable-revalidate-edge/no-store/page.js", - "variable-revalidate-edge/no-store/page_client-reference-manifest.js", - "variable-revalidate-edge/post-method-request/page.js", - "variable-revalidate-edge/post-method-request/page_client-reference-manifest.js", - "variable-revalidate-edge/post-method/page.js", - "variable-revalidate-edge/post-method/page_client-reference-manifest.js", - "variable-revalidate-edge/revalidate-3/page.js", - "variable-revalidate-edge/revalidate-3/page_client-reference-manifest.js", - "variable-revalidate/authorization.html", - "variable-revalidate/authorization.rsc", - "variable-revalidate/authorization/page.js", - "variable-revalidate/authorization/page_client-reference-manifest.js", - "variable-revalidate/cookie.html", - "variable-revalidate/cookie.rsc", - "variable-revalidate/cookie/page.js", - "variable-revalidate/cookie/page_client-reference-manifest.js", - "variable-revalidate/encoding.html", - "variable-revalidate/encoding.rsc", - "variable-revalidate/encoding/page.js", - "variable-revalidate/encoding/page_client-reference-manifest.js", - "variable-revalidate/headers-instance.html", - "variable-revalidate/headers-instance.rsc", - "variable-revalidate/headers-instance/page.js", - "variable-revalidate/headers-instance/page_client-reference-manifest.js", - "variable-revalidate/no-store/page.js", - "variable-revalidate/no-store/page_client-reference-manifest.js", - "variable-revalidate/post-method-request/page.js", - "variable-revalidate/post-method-request/page_client-reference-manifest.js", - "variable-revalidate/post-method.html", - "variable-revalidate/post-method.rsc", - "variable-revalidate/post-method/page.js", - "variable-revalidate/post-method/page_client-reference-manifest.js", - "variable-revalidate/revalidate-3.html", - "variable-revalidate/revalidate-3.rsc", - "variable-revalidate/revalidate-3/page.js", - "variable-revalidate/revalidate-3/page_client-reference-manifest.js", - "variable-revalidate/revalidate-360-isr.html", - "variable-revalidate/revalidate-360-isr.rsc", - "variable-revalidate/revalidate-360-isr/page.js", - "variable-revalidate/revalidate-360-isr/page_client-reference-manifest.js", - "variable-revalidate/revalidate-360/page.js", - "variable-revalidate/revalidate-360/page_client-reference-manifest.js", - "variable-revalidate/status-code/page.js", - "variable-revalidate/status-code/page_client-reference-manifest.js", - ] - `) + 'page.js', + 'index.rsc', + 'index.html', + 'blog/seb.rsc', + 'blog/tim.rsc', + '_not-found.js', + 'blog/seb.html', + 'blog/tim.html', + 'isr-error-handling.rsc', + '_not-found.rsc', + '_not-found.html', + 'blog/styfle.rsc', + 'force-cache.rsc', + 'blog/styfle.html', + 'force-cache.html', + 'isr-error-handling/page.js', + 'ssg-draft-mode.rsc', + 'ssr-forced/page.js', + 'articles/works.rsc', + 'force-cache/page.js', + 'force-cache/large-data/page.js', + 'force-cache/large-data/page_client-reference-manifest.js', + 'ssg-draft-mode.html', + 'articles/works.html', + 'no-store/static.rsc', + '(new)/custom/page.js', + 'force-static/page.js', + 'response-url/page.js', + 'no-store/static.html', + 'blog/[author]/page.js', + 'default-cache/page.js', + 'fetch-no-cache/page.js', + 'force-no-store/page.js', + 'force-static-fetch-no-store.html', + 'force-static-fetch-no-store.rsc', + 'force-static-fetch-no-store/page.js', + 'force-static-fetch-no-store/page_client-reference-manifest.js', + 'force-static/first.rsc', + 'api/draft-mode/route.js', + 'api/large-data/route.js', + 'blog/tim/first-post.rsc', + 'force-static/first.html', + 'force-static/second.rsc', + 'ssg-draft-mode/test.rsc', + 'isr-error-handling.html', + 'articles/[slug]/page.js', + 'no-store/static/page.js', + 'blog/seb/second-post.rsc', + 'blog/tim/first-post.html', + 'force-static/second.html', + 'ssg-draft-mode/test.html', + 'no-store/dynamic/page.js', + 'blog/seb/second-post.html', + 'ssg-draft-mode/test-2.rsc', + 'blog/styfle/first-post.rsc', + 'dynamic-error/[id]/page.js', + 'ssg-draft-mode/test-2.html', + 'blog/styfle/first-post.html', + 'blog/styfle/second-post.rsc', + 'force-static/[slug]/page.js', + 'hooks/use-pathname/slug.rsc', + 'route-handler/post/route.js', + 'blog/[author]/[slug]/page.js', + 'blog/styfle/second-post.html', + 'hooks/use-pathname/slug.html', + 'flight/[slug]/[slug2]/page.js', + 'variable-revalidate/cookie.rsc', + 'ssr-auto/cache-no-store/page.js', + 'variable-revalidate/cookie.html', + 'api/revalidate-tag-edge/route.js', + 'api/revalidate-tag-node/route.js', + 'variable-revalidate/encoding.rsc', + 'api/revalidate-path-edge/route.js', + 'api/revalidate-path-node/route.js', + 'gen-params-dynamic/[slug]/page.js', + 'hooks/use-pathname/[slug]/page.js', + 'page_client-reference-manifest.js', + 'react-fetch-deduping-edge/page.js', + 'react-fetch-deduping-node/page.js', + 'variable-revalidate/encoding.html', + 'variable-revalidate/cookie/page.js', + 'ssg-draft-mode/[[...route]]/page.js', + 'variable-revalidate/post-method.rsc', + 'stale-cache-serving/app-page/page.js', + 'dynamic-no-gen-params/[slug]/page.js', + 'static-to-dynamic-error/[id]/page.js', + 'variable-revalidate/encoding/page.js', + 'variable-revalidate/no-store/page.js', + 'variable-revalidate/post-method.html', + 'variable-revalidate/revalidate-3.rsc', + 'gen-params-dynamic-revalidate/one.rsc', + 'route-handler/revalidate-360/route.js', + 'route-handler/static-cookies/route.js', + 'variable-revalidate-edge/body/page.js', + 'variable-revalidate/authorization.rsc', + 'variable-revalidate/revalidate-3.html', + 'force-dynamic-prerender/[slug]/page.js', + 'gen-params-dynamic-revalidate/one.html', + 'ssr-auto/fetch-revalidate-zero/page.js', + 'variable-revalidate/authorization.html', + '_not-found_client-reference-manifest.js', + 'force-dynamic-no-prerender/[id]/page.js', + 'variable-revalidate/post-method/page.js', + 'variable-revalidate/status-code/page.js', + 'dynamic-no-gen-params-ssr/[slug]/page.js', + 'hooks/use-search-params/force-static.rsc', + 'partial-gen-params/[lang]/[slug]/page.js', + 'variable-revalidate/headers-instance.rsc', + 'variable-revalidate/revalidate-3/page.js', + 'stale-cache-serving-edge/app-page/page.js', + 'hooks/use-search-params/force-static.html', + 'hooks/use-search-params/with-suspense.rsc', + 'route-handler/revalidate-360-isr/route.js', + 'variable-revalidate-edge/encoding/page.js', + 'variable-revalidate-edge/no-store/page.js', + 'variable-revalidate/authorization/page.js', + 'variable-revalidate/headers-instance.html', + 'stale-cache-serving/route-handler/route.js', + 'hooks/use-search-params/with-suspense.html', + 'route-handler-edge/revalidate-360/route.js', + 'variable-revalidate/revalidate-360-isr.rsc', + 'variable-revalidate/revalidate-360/page.js', + 'static-to-dynamic-error-forced/[id]/page.js', + 'variable-config-revalidate/revalidate-3.rsc', + 'variable-revalidate/revalidate-360-isr.html', + 'isr-error-handling/page_client-reference-manifest.js', + 'gen-params-dynamic-revalidate/[slug]/page.js', + 'hooks/use-search-params/force-static/page.js', + 'ssr-forced/page_client-reference-manifest.js', + 'variable-config-revalidate/revalidate-3.html', + 'variable-revalidate-edge/post-method/page.js', + 'variable-revalidate/headers-instance/page.js', + 'force-cache/page_client-reference-manifest.js', + 'hooks/use-search-params/with-suspense/page.js', + 'variable-revalidate-edge/revalidate-3/page.js', + '(new)/custom/page_client-reference-manifest.js', + 'force-static/page_client-reference-manifest.js', + 'response-url/page_client-reference-manifest.js', + 'variable-revalidate/revalidate-360-isr/page.js', + 'stale-cache-serving-edge/route-handler/route.js', + 'blog/[author]/page_client-reference-manifest.js', + 'default-cache/page_client-reference-manifest.js', + 'variable-config-revalidate/revalidate-3/page.js', + 'variable-revalidate/post-method-request/page.js', + 'fetch-no-cache/page_client-reference-manifest.js', + 'force-dynamic-catch-all/[slug]/[[...id]]/page.js', + 'force-no-store/page_client-reference-manifest.js', + 'partial-gen-params-no-additional-lang/en/RAND.rsc', + 'partial-gen-params-no-additional-lang/fr/RAND.rsc', + 'partial-gen-params-no-additional-slug/en/RAND.rsc', + 'partial-gen-params-no-additional-slug/fr/RAND.rsc', + 'articles/[slug]/page_client-reference-manifest.js', + 'no-store/static/page_client-reference-manifest.js', + 'partial-gen-params-no-additional-lang/en/RAND.html', + 'partial-gen-params-no-additional-lang/en/first.rsc', + 'partial-gen-params-no-additional-lang/fr/RAND.html', + 'partial-gen-params-no-additional-lang/fr/first.rsc', + 'partial-gen-params-no-additional-slug/en/RAND.html', + 'partial-gen-params-no-additional-slug/en/first.rsc', + 'partial-gen-params-no-additional-slug/fr/RAND.html', + 'partial-gen-params-no-additional-slug/fr/first.rsc', + 'no-store/dynamic/page_client-reference-manifest.js', + 'partial-gen-params-no-additional-lang/en/first.html', + 'partial-gen-params-no-additional-lang/en/second.rsc', + 'partial-gen-params-no-additional-lang/fr/first.html', + 'partial-gen-params-no-additional-lang/fr/second.rsc', + 'partial-gen-params-no-additional-slug/en/first.html', + 'partial-gen-params-no-additional-slug/en/second.rsc', + 'partial-gen-params-no-additional-slug/fr/first.html', + 'partial-gen-params-no-additional-slug/fr/second.rsc', + 'dynamic-error/[id]/page_client-reference-manifest.js', + 'partial-gen-params-no-additional-lang/en/second.html', + 'partial-gen-params-no-additional-lang/fr/second.html', + 'partial-gen-params-no-additional-slug/en/second.html', + 'partial-gen-params-no-additional-slug/fr/second.html', + 'variable-revalidate-edge/post-method-request/page.js', + 'force-static/[slug]/page_client-reference-manifest.js', + 'blog/[author]/[slug]/page_client-reference-manifest.js', + 'flight/[slug]/[slug2]/page_client-reference-manifest.js', + 'hooks/use-search-params/static-bailout.html', + 'hooks/use-search-params/static-bailout.rsc', + 'hooks/use-search-params/static-bailout/page.js', + 'hooks/use-search-params/static-bailout/page_client-reference-manifest.js', + 'ssr-auto/cache-no-store/page_client-reference-manifest.js', + 'gen-params-dynamic/[slug]/page_client-reference-manifest.js', + 'hooks/use-pathname/[slug]/page_client-reference-manifest.js', + 'partial-gen-params-no-additional-lang/[lang]/[slug]/page.js', + 'partial-gen-params-no-additional-slug/[lang]/[slug]/page.js', + 'react-fetch-deduping-edge/page_client-reference-manifest.js', + 'react-fetch-deduping-node/page_client-reference-manifest.js', + 'variable-revalidate/cookie/page_client-reference-manifest.js', + 'ssg-draft-mode/[[...route]]/page_client-reference-manifest.js', + 'stale-cache-serving/app-page/page_client-reference-manifest.js', + 'dynamic-no-gen-params/[slug]/page_client-reference-manifest.js', + 'static-to-dynamic-error/[id]/page_client-reference-manifest.js', + 'variable-revalidate/encoding/page_client-reference-manifest.js', + 'variable-revalidate/no-store/page_client-reference-manifest.js', + 'variable-revalidate-edge/body/page_client-reference-manifest.js', + 'force-dynamic-prerender/[slug]/page_client-reference-manifest.js', + 'ssr-auto/fetch-revalidate-zero/page_client-reference-manifest.js', + 'force-dynamic-no-prerender/[id]/page_client-reference-manifest.js', + 'variable-revalidate/post-method/page_client-reference-manifest.js', + 'variable-revalidate/status-code/page_client-reference-manifest.js', + 'dynamic-no-gen-params-ssr/[slug]/page_client-reference-manifest.js', + 'partial-gen-params/[lang]/[slug]/page_client-reference-manifest.js', + 'variable-revalidate/revalidate-3/page_client-reference-manifest.js', + 'stale-cache-serving-edge/app-page/page_client-reference-manifest.js', + 'variable-revalidate-edge/encoding/page_client-reference-manifest.js', + 'variable-revalidate-edge/no-store/page_client-reference-manifest.js', + 'variable-revalidate/authorization/page_client-reference-manifest.js', + 'variable-revalidate/revalidate-360/page_client-reference-manifest.js', + 'static-to-dynamic-error-forced/[id]/page_client-reference-manifest.js', + 'gen-params-dynamic-revalidate/[slug]/page_client-reference-manifest.js', + 'hooks/use-search-params/force-static/page_client-reference-manifest.js', + 'variable-revalidate-edge/post-method/page_client-reference-manifest.js', + 'variable-revalidate/headers-instance/page_client-reference-manifest.js', + 'hooks/use-search-params/with-suspense/page_client-reference-manifest.js', + 'variable-revalidate-edge/revalidate-3/page_client-reference-manifest.js', + 'variable-revalidate/revalidate-360-isr/page_client-reference-manifest.js', + 'variable-config-revalidate/revalidate-3/page_client-reference-manifest.js', + 'variable-revalidate/post-method-request/page_client-reference-manifest.js', + 'force-dynamic-catch-all/[slug]/[[...id]]/page_client-reference-manifest.js', + 'variable-revalidate-edge/post-method-request/page_client-reference-manifest.js', + 'partial-gen-params-no-additional-lang/[lang]/[slug]/page_client-reference-manifest.js', + 'partial-gen-params-no-additional-slug/[lang]/[slug]/page_client-reference-manifest.js', + ].sort() + ) }) it('should have correct prerender-manifest entries', async () => { @@ -1029,6 +1033,22 @@ createNextDescribe( "initialRevalidateSeconds": false, "srcRoute": "/hooks/use-search-params/force-static", }, + "/hooks/use-search-params/static-bailout": { + "dataRoute": "/hooks/use-search-params/static-bailout.rsc", + "experimentalBypassFor": [ + { + "key": "Next-Action", + "type": "header", + }, + { + "key": "content-type", + "type": "header", + "value": "multipart/form-data", + }, + ], + "initialRevalidateSeconds": false, + "srcRoute": "/hooks/use-search-params/static-bailout", + }, "/hooks/use-search-params/with-suspense": { "dataRoute": "/hooks/use-search-params/with-suspense.rsc", "experimentalBypassFor": [ @@ -2868,6 +2888,26 @@ createNextDescribe( describe('useSearchParams', () => { describe('client', () => { + it('should bailout to client rendering - without suspense boundary', async () => { + const url = + '/hooks/use-search-params/static-bailout?first=value&second=other&third' + const browser = await next.browser(url) + + expect(await browser.elementByCss('#params-first').text()).toBe( + 'value' + ) + expect(await browser.elementByCss('#params-second').text()).toBe( + 'other' + ) + expect(await browser.elementByCss('#params-third').text()).toBe('') + expect(await browser.elementByCss('#params-not-real').text()).toBe( + 'N/A' + ) + + const $ = await next.render$(url) + expect($('meta[content=noindex]').length).toBe(0) + }) + it('should bailout to client rendering - with suspense boundary', async () => { const url = '/hooks/use-search-params/with-suspense?first=value&second=other&third' @@ -2937,6 +2977,14 @@ createNextDescribe( // Don't run these tests in dev mode since they won't be statically generated if (!isDev) { describe('server response', () => { + it('should bailout to client rendering - without suspense boundary', async () => { + const res = await next.fetch( + '/hooks/use-search-params/static-bailout' + ) + const html = await res.text() + expect(html).toInclude('') + }) + it('should bailout to client rendering - with suspense boundary', async () => { const res = await next.fetch( '/hooks/use-search-params/with-suspense' diff --git a/test/e2e/app-dir/app-static/app/hooks/use-search-params/static-bailout/page.js b/test/e2e/app-dir/app-static/app/hooks/use-search-params/static-bailout/page.js new file mode 100644 index 0000000000..bdf2409132 --- /dev/null +++ b/test/e2e/app-dir/app-static/app/hooks/use-search-params/static-bailout/page.js @@ -0,0 +1,10 @@ +import UseSearchParams from '../search-params' + +export default function Page() { + return ( + <> +

+ + + ) +} diff --git a/test/e2e/app-dir/app-static/next.config.js b/test/e2e/app-dir/app-static/next.config.js index 57153652e4..bc0912e06b 100644 --- a/test/e2e/app-dir/app-static/next.config.js +++ b/test/e2e/app-dir/app-static/next.config.js @@ -13,7 +13,7 @@ module.exports = { afterFiles: [ { source: '/rewritten-use-search-params', - destination: '/hooks/use-search-params/with-suspense', + destination: '/hooks/use-search-params/static-bailout', }, { source: '/rewritten-use-pathname', diff --git a/test/e2e/app-dir/hooks/app/hooks/use-search-params/page.js b/test/e2e/app-dir/hooks/app/hooks/use-search-params/page.js index 6c8e29a6cc..d84e0f522c 100644 --- a/test/e2e/app-dir/hooks/app/hooks/use-search-params/page.js +++ b/test/e2e/app-dir/hooks/app/hooks/use-search-params/page.js @@ -1,17 +1,8 @@ 'use client' import { useSearchParams } from 'next/navigation' -import { Suspense } from 'react' export default function Page() { - return ( - Loading...}> - - - ) -} - -function Component() { const params = useSearchParams() return ( diff --git a/test/e2e/app-dir/not-found-default/app/layout.js b/test/e2e/app-dir/not-found-default/app/layout.js index bd97c16add..5b344e2614 100644 --- a/test/e2e/app-dir/not-found-default/app/layout.js +++ b/test/e2e/app-dir/not-found-default/app/layout.js @@ -13,9 +13,7 @@ export default function Root({ children }) { return ( - Loading...}> - - + diff --git a/test/e2e/app-dir/use-search-params/app/layout-no-suspense.js b/test/e2e/app-dir/use-search-params/app/layout-no-suspense.js deleted file mode 100644 index f3791f288f..0000000000 --- a/test/e2e/app-dir/use-search-params/app/layout-no-suspense.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function Layout({ children }) { - return ( - - - {children} - - ) -} diff --git a/test/e2e/app-dir/use-search-params/app/layout.js b/test/e2e/app-dir/use-search-params/app/layout.js deleted file mode 100644 index 06c28b2c36..0000000000 --- a/test/e2e/app-dir/use-search-params/app/layout.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' - -export default function Layout({ children }) { - return ( - - - - Loading...}> - {children} - - - - ) -} diff --git a/test/e2e/app-dir/use-search-params/app/page.js b/test/e2e/app-dir/use-search-params/app/page.js deleted file mode 100644 index db29d22f58..0000000000 --- a/test/e2e/app-dir/use-search-params/app/page.js +++ /dev/null @@ -1,8 +0,0 @@ -'use client' - -import { useSearchParams } from 'next/navigation' - -export default function Page() { - useSearchParams() - return

Page
-} diff --git a/test/e2e/app-dir/use-search-params/next.config.js b/test/e2e/app-dir/use-search-params/next.config.js deleted file mode 100644 index 459c99acaf..0000000000 --- a/test/e2e/app-dir/use-search-params/next.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import("next").NextConfig} */ -const config = { - experimental: { - missingSuspenseWithCSRBailout: true, - }, -} - -module.exports = config diff --git a/test/e2e/app-dir/use-search-params/use-search-params.test.ts b/test/e2e/app-dir/use-search-params/use-search-params.test.ts deleted file mode 100644 index 61de9db81a..0000000000 --- a/test/e2e/app-dir/use-search-params/use-search-params.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { createNextDescribe } from 'e2e-utils' - -createNextDescribe( - 'use-search-params', - { files: __dirname, skipStart: true }, - ({ next, isNextStart }) => { - if (!isNextStart) { - it('skip test for dev mode', () => {}) - return - } - - const message = `useSearchParams() should be wrapped in a suspense boundary at page "/".` - - it('should pass build if useSearchParams is wrapped in a suspense boundary', async () => { - await expect(next.build()).resolves.toEqual({ - exitCode: 0, - cliOutput: expect.not.stringContaining(message), - }) - }) - - it('should fail build if useSearchParams is not wrapped in a suspense boundary', async () => { - await next.clean() - await next.renameFile('app/layout.js', 'app/layout-suspense.js') - await next.renameFile('app/layout-no-suspense.js', 'app/layout.js') - - await expect(next.build()).resolves.toEqual({ - exitCode: 1, - cliOutput: expect.stringContaining(message), - }) - - await next.renameFile('app/layout.js', 'app/layout-no-suspense.js') - await next.renameFile('app/layout-suspense.js', 'app/layout.js') - }) - } -) diff --git a/test/lib/next-modes/base.ts b/test/lib/next-modes/base.ts index 530d280fa8..5d9832d48c 100644 --- a/test/lib/next-modes/base.ts +++ b/test/lib/next-modes/base.ts @@ -1,6 +1,6 @@ import os from 'os' import path from 'path' -import { existsSync, promises as fs } from 'fs' +import fs from 'fs-extra' import treeKill from 'tree-kill' import type { NextConfig } from 'next' import { FileRef } from '../e2e-utils' @@ -100,18 +100,17 @@ export class NextInstance { `FileRef passed to "files" in "createNext" is not a directory ${files.fsPath}` ) } - - await fs.cp(files.fsPath, this.testDir, { recursive: true }) + await fs.copy(files.fsPath, this.testDir) } else { for (const filename of Object.keys(files)) { const item = files[filename] const outputFilename = path.join(this.testDir, filename) if (typeof item === 'string') { - await fs.mkdir(path.dirname(outputFilename), { recursive: true }) + await fs.ensureDir(path.dirname(outputFilename)) await fs.writeFile(outputFilename, item) } else { - await fs.cp(item.fsPath, outputFilename, { recursive: true }) + await fs.copy(item.fsPath, outputFilename) } } } @@ -159,7 +158,7 @@ export class NextInstance { if (skipInstall || skipIsolatedNext) { const pkgScripts = (this.packageJson['scripts'] as {}) || {} - await fs.mkdir(this.testDir, { recursive: true }) + await fs.ensureDir(this.testDir) await fs.writeFile( path.join(this.testDir, 'package.json'), JSON.stringify( @@ -194,9 +193,7 @@ export class NextInstance { !this.packageJson && !(global as any).isNextDeploy ) { - await fs.cp(process.env.NEXT_TEST_STARTER, this.testDir, { - recursive: true, - }) + await fs.copy(process.env.NEXT_TEST_STARTER, this.testDir) } else { const { installDir } = await createNextInstall({ parentSpan: rootSpan, @@ -221,7 +218,7 @@ export class NextInstance { file.startsWith('next.config.') ) - if (existsSync(path.join(this.testDir, 'next.config.js'))) { + if (await fs.pathExists(path.join(this.testDir, 'next.config.js'))) { nextConfigFile = 'next.config.js' } @@ -333,10 +330,7 @@ export class NextInstance { ] for (const file of await fs.readdir(this.testDir)) { if (!keptFiles.includes(file)) { - await fs.rm(path.join(this.testDir, file), { - recursive: true, - force: true, - }) + await fs.remove(path.join(this.testDir, file)) } } await this.writeInitialFiles() @@ -384,7 +378,7 @@ export class NextInstance { if (process.env.TRACE_PLAYWRIGHT) { await fs - .cp( + .copy( path.join(this.testDir, '.next/trace'), path.join( __dirname, @@ -396,8 +390,7 @@ export class NextInstance { ) .replace(/\//g, '-')}`, `next-trace` - ), - { recursive: true } + ) ) .catch((e) => { require('console').error(e) @@ -405,7 +398,7 @@ export class NextInstance { } if (!process.env.NEXT_TEST_SKIP_CLEANUP) { - await fs.rm(this.testDir, { recursive: true, force: true }) + await fs.remove(this.testDir) } require('console').log(`destroyed next instance`) } catch (err) { @@ -431,15 +424,13 @@ export class NextInstance { // TODO: block these in deploy mode public async hasFile(filename: string) { - return existsSync(path.join(this.testDir, filename)) + return fs.pathExists(path.join(this.testDir, filename)) } public async readFile(filename: string) { return fs.readFile(path.join(this.testDir, filename), 'utf8') } public async readJSON(filename: string) { - return JSON.parse( - await fs.readFile(path.join(this.testDir, filename), 'utf-8') - ) + return fs.readJSON(path.join(this.testDir, filename)) } private async handleDevWatchDelayBeforeChange(filename: string) { // This is a temporary workaround for turbopack starting watching too late. @@ -471,8 +462,8 @@ export class NextInstance { await this.handleDevWatchDelayBeforeChange(filename) const outputPath = path.join(this.testDir, filename) - const newFile = !existsSync(outputPath) - await fs.mkdir(path.dirname(outputPath), { recursive: true }) + const newFile = !(await fs.pathExists(outputPath)) + await fs.ensureDir(path.dirname(outputPath)) await fs.writeFile( outputPath, typeof content === 'function' @@ -495,13 +486,12 @@ export class NextInstance { path.join(this.testDir, filename), path.join(this.testDir, newFilename) ) - await this.handleDevWatchDelayAfterChange(filename) } public async renameFolder(foldername: string, newFoldername: string) { await this.handleDevWatchDelayBeforeChange(foldername) - await fs.rename( + await fs.move( path.join(this.testDir, foldername), path.join(this.testDir, newFoldername) ) @@ -510,11 +500,7 @@ export class NextInstance { public async deleteFile(filename: string) { await this.handleDevWatchDelayBeforeChange(filename) - await fs.rm(path.join(this.testDir, filename), { - recursive: true, - force: true, - }) - + await fs.remove(path.join(this.testDir, filename)) await this.handleDevWatchDelayAfterChange(filename) }