diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index a19c4477d793..69d30e7357d4 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -9,6 +9,7 @@ import type { PrerenderManifest } from '../build' import type { Rewrite } from '../lib/load-custom-routes' import type { BaseNextRequest, BaseNextResponse } from './base-http' import type { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' +import type { PayloadOptions } from './send-payload' import { execOnce } from '../shared/lib/utils' import { @@ -43,7 +44,7 @@ import { route } from './router' import { run } from './web/sandbox' import { NodeNextRequest, NodeNextResponse } from './base-http/node' -import { PayloadOptions, sendRenderResult } from './send-payload' +import { sendRenderResult } from './send-payload' import { getExtension, serveStatic } from './serve-static' import { ParsedUrlQuery } from 'querystring' import { apiResolver } from './api-utils/node' @@ -584,6 +585,12 @@ export default class NextNodeServer extends BaseServer { protected streamResponseChunk(res: NodeNextResponse, chunk: any) { res.originalResponse.write(chunk) + + // When both compression and streaming are enabled, we need to explicitly + // flush the response to avoid it being buffered by gzip. + if (this.compression && 'flush' in res.originalResponse) { + ;(res.originalResponse as any).flush() + } } protected async imageOptimizer( diff --git a/packages/next/server/web-server.ts b/packages/next/server/web-server.ts index b3e11bc0bf4a..cb0c8b5eba47 100644 --- a/packages/next/server/web-server.ts +++ b/packages/next/server/web-server.ts @@ -148,6 +148,20 @@ export default class NextWebServer extends BaseServer { options?: PayloadOptions | undefined } ): Promise { + // Add necessary headers. + // @TODO: Share the isomorphic logic with server/send-payload.ts. + if (options.poweredByHeader && options.type === 'html') { + res.setHeader('X-Powered-By', 'Next.js') + } + if (!res.getHeader('Content-Type')) { + res.setHeader( + 'Content-Type', + options.type === 'json' + ? 'application/json' + : 'text/html; charset=utf-8' + ) + } + // @TODO const writer = res.transformStream.writable.getWriter() diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index 79a161d66862..d93f233a1aab 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -177,6 +177,12 @@ describe('Edge runtime - dev', () => { expect(content).toMatchInlineSnapshot('"foo.client"') }) + it('should have content-type and content-encoding headers', async () => { + const res = await fetchViaHTTP(context.appPort, '/') + expect(res.headers.get('content-type')).toBe('text/html; charset=utf-8') + expect(res.headers.get('content-encoding')).toBe('gzip') + }) + basic(context, { env: 'dev' }) streaming(context) rsc(context, { runtime: 'edge', env: 'dev' })