Apply react-server condition for pages api (#57459)

Apply react-server condition and related API checks for pages API.

if you're doing react SSR with renderToString in middleware it should be disallowed. Imaging it could send the rendered html code to client and you display it in browser. But it might require hydration so it can be broken.

Follow up for #57448 , same reason explained in #57448
Closes NEXT-1653
This commit is contained in:
Jiachi Liu 2023-10-25 19:07:27 -07:00 committed by GitHub
parent b27aa57908
commit 6b18f397cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 45 additions and 59 deletions

View file

@ -454,13 +454,12 @@ export default async function getBaseWebpackConfig(
].filter(Boolean)
: []
const swcLoaderForMiddlewareLayer = useSWCLoader
? swcServerLayerLoader
: // When using Babel, we will have to use SWC to do the optimization
// for middleware to tree shake the unused default optimized imports like "next/server".
// This will cause some performance overhead but
// acceptable as Babel will not be recommended.
[swcServerLayerLoader, babelLoader]
const swcLoaderForMiddlewareLayer =
// When using Babel, we will have to use SWC to do the optimization
// for middleware to tree shake the unused default optimized imports like "next/server".
// This will cause some performance overhead but
// acceptable as Babel will not be recommended.
[swcServerLayerLoader, babelLoader].filter(Boolean)
// client components layers: SSR + browser
const swcLoaderForClientLayer = [
@ -488,16 +487,9 @@ export default async function getBaseWebpackConfig(
: []),
]
// Loader for API routes needs to be differently configured as it shouldn't
// have RSC transpiler enabled, so syntax checks such as invalid imports won't
// be performed.
const loaderForAPIRoutes =
hasAppDir && useSWCLoader
? getSwcLoader({
serverComponents: false,
isReactServerLayer: false,
})
: defaultLoaders.babel
// Loader for API routes will also apply react-server export condition for bundling,
// and the API checks for server side only APIs.
const loaderForAPIRoutes = [swcServerLayerLoader, babelLoader].filter(Boolean)
const pageExtensions = config.pageExtensions

View file

@ -1,7 +1,3 @@
// You can still import React and Next's client component APIs from the server
// they won't be poisoned by the environment.
// eslint-disable-next-line no-unused-vars
import { useState } from 'react'
import 'next/headers'
export default function (_, res) {

View file

@ -1,5 +1,5 @@
import { createNextDescribe } from 'e2e-utils'
import { getRedboxSource, hasRedbox } from 'next-test-utils'
import { check, getRedboxSource, hasRedbox } from 'next-test-utils'
createNextDescribe(
'module layer',
@ -68,51 +68,49 @@ createNextDescribe(
// Should error for using mixed (with client-only) in server targets
if (isNextDev) {
describe('no server-only in server targets', () => {
const middlewareFile = 'middleware.js'
// const pagesApiFile = 'pages/api/hello.js'
let middlewareContent = ''
// let pagesApiContent = ''
beforeAll(async () => {
await next.stop()
middlewareContent = await next.readFile(middlewareFile)
// pagesApiContent = await next.readFile(pagesApiFile)
it('should error when import client-only in middleware', async () => {
const middlewareFile = 'middleware.js'
const middlewareContent = await next.readFile(middlewareFile)
await next.patchFile(
middlewareFile,
middlewareContent
// .replace("import 'server-only'", "// import 'server-only'")
.replace(
"// import './lib/mixed-lib'",
"import './lib/mixed-lib'"
)
middlewareContent.replace(
"// import './lib/mixed-lib'",
"import './lib/mixed-lib'"
)
)
// await next.patchFile(
// pagesApiFile,
// pagesApiContent
// .replace("import 'server-only'", "// import 'server-only'")
// .replace(
// "// import '../../lib/mixed-lib'",
// "import '../../lib/mixed-lib'"
// )
// )
await next.start()
})
afterAll(async () => {
await next.patchFile(middlewareFile, middlewareContent)
// await next.patchFile(pagesApiFile, pagesApiContent)
})
it('should error when import client-only in middleware', async () => {
const browser = await next.browser('/')
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toContain(
`You're importing a component that imports client-only. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.`
)
await next.patchFile(middlewareFile, middlewareContent)
})
it('should error when import client-only in pages/api', async () => {
const pagesApiFile = 'pages/api/mixed.js'
const pagesApiContent = await next.readFile(pagesApiFile)
await next.patchFile(
pagesApiFile,
pagesApiContent.replace(
"// import 'client-only'",
"import 'client-only'"
)
)
const existingCliOutputLength = next.cliOutput.length
await check(async () => {
await next.fetch('/api/mixed')
const newCliOutput = next.cliOutput.slice(existingCliOutputLength)
expect(newCliOutput).toContain('./pages/api/mixed.js')
expect(newCliOutput).toContain(
`You're importing a component that imports client-only. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.`
)
return 'success'
}, 'success')
await next.patchFile(pagesApiFile, pagesApiContent)
})
})
}

View file

@ -1,4 +1,4 @@
import '../../lib/mixed-lib'
// import 'client-only'
export default function handler(req, res) {
return res.send('pages/api/mixed.js:')