diff --git a/packages/next/src/server/api-utils/get-cookie-parser.ts b/packages/next/src/server/api-utils/get-cookie-parser.ts new file mode 100644 index 0000000000000..8477c5a0a6edc --- /dev/null +++ b/packages/next/src/server/api-utils/get-cookie-parser.ts @@ -0,0 +1,21 @@ +import type { NextApiRequestCookies } from '.' + +/** + * Parse cookies from the `headers` of request + * @param req request object + */ + +export function getCookieParser(headers: { + [key: string]: string | string[] | null | undefined +}): () => NextApiRequestCookies { + return function parseCookie(): NextApiRequestCookies { + const { cookie } = headers + + if (!cookie) { + return {} + } + + const { parse: parseCookieFn } = require('next/dist/compiled/cookie') + return parseCookieFn(Array.isArray(cookie) ? cookie.join('; ') : cookie) + } +} diff --git a/packages/next/src/server/api-utils/index.ts b/packages/next/src/server/api-utils/index.ts index 58093398099e1..34494d71af7f5 100644 --- a/packages/next/src/server/api-utils/index.ts +++ b/packages/next/src/server/api-utils/index.ts @@ -18,25 +18,6 @@ export type __ApiPreviewProps = { previewModeSigningKey: string } -/** - * Parse cookies from the `headers` of request - * @param req request object - */ -export function getCookieParser(headers: { - [key: string]: string | string[] | null | undefined -}): () => NextApiRequestCookies { - return function parseCookie(): NextApiRequestCookies { - const { cookie } = headers - - if (!cookie) { - return {} - } - - const { parse: parseCookieFn } = require('next/dist/compiled/cookie') - return parseCookieFn(Array.isArray(cookie) ? cookie.join('; ') : cookie) - } -} - /** * * @param res response object diff --git a/packages/next/src/server/api-utils/node.ts b/packages/next/src/server/api-utils/node.ts index 40ea977cf9f9b..1b4d77d007f1a 100644 --- a/packages/next/src/server/api-utils/node.ts +++ b/packages/next/src/server/api-utils/node.ts @@ -15,7 +15,6 @@ import isError from '../../lib/is-error' import { isResSent } from '../../shared/lib/utils' import { interopDefault } from '../../lib/interop-default' import { - getCookieParser, setLazyProp, sendStatusCode, redirect, @@ -27,6 +26,7 @@ import { SYMBOL_PREVIEW_DATA, RESPONSE_LIMIT_DEFAULT, } from './index' +import { getCookieParser } from './get-cookie-parser' import { getTracer } from '../lib/trace/tracer' import { NodeSpan } from '../lib/trace/constants' import { RequestCookies } from '../web/spec-extension/cookies' diff --git a/packages/next/src/server/base-http/index.ts b/packages/next/src/server/base-http/index.ts index bb4b4bfa5cdff..218656f861dcf 100644 --- a/packages/next/src/server/base-http/index.ts +++ b/packages/next/src/server/base-http/index.ts @@ -2,7 +2,8 @@ import type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'http' import type { I18NConfig } from '../config-shared' import { PERMANENT_REDIRECT_STATUS } from '../../shared/lib/constants' -import { getCookieParser, NextApiRequestCookies } from '../api-utils' +import { NextApiRequestCookies } from '../api-utils' +import { getCookieParser } from '../api-utils/get-cookie-parser' export interface BaseNextRequestConfig { basePath: string | undefined diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 4e6ed0a307a49..e7b8473908401 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -50,11 +50,7 @@ import { TEMPORARY_REDIRECT_STATUS, } from '../shared/lib/constants' import { isDynamicRoute } from '../shared/lib/router/utils' -import { - setLazyProp, - getCookieParser, - checkIsOnDemandRevalidate, -} from './api-utils' +import { checkIsOnDemandRevalidate } from './api-utils' import { setConfig } from '../shared/lib/runtime-config.shared-runtime' import { setRevalidateHeaders } from './send-payload/revalidate-headers' @@ -791,8 +787,6 @@ export default abstract class Server { return } - setLazyProp({ req: req as any }, 'cookies', getCookieParser(req.headers)) - // Parse url if parsedUrl not provided if (!parsedUrl || typeof parsedUrl !== 'object') { parsedUrl = parseUrl(req.url!, true) diff --git a/packages/next/src/server/lib/router-utils/resolve-routes.ts b/packages/next/src/server/lib/router-utils/resolve-routes.ts index 11097b180bc8c..7a89e9371cf75 100644 --- a/packages/next/src/server/lib/router-utils/resolve-routes.ts +++ b/packages/next/src/server/lib/router-utils/resolve-routes.ts @@ -15,7 +15,6 @@ import { stringifyQuery } from '../../server-route-utils' import { formatHostname } from '../format-hostname' import { toNodeOutgoingHttpHeaders } from '../../web/utils' import { isAbortError } from '../../pipe-readable' -import { getCookieParser, setLazyProp } from '../../api-utils' import { getHostname } from '../../../shared/lib/get-hostname' import { UnwrapPromise } from '../../../lib/coalesced-function' import { getRedirectStatus } from '../../../lib/redirect-status' @@ -154,7 +153,6 @@ export function getResolveRoutes( addRequestMeta(req, '__NEXT_INIT_URL', initUrl) addRequestMeta(req, '__NEXT_INIT_QUERY', { ...parsedUrl.query }) addRequestMeta(req, '_protocol', protocol) - setLazyProp({ req }, 'cookies', () => getCookieParser(req.headers)()) if (!isUpgradeReq) { addRequestMeta(req, '__NEXT_CLONABLE_BODY', getCloneableBody(req)) diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx index 9a9843d9e32ec..0cd5c24f09245 100644 --- a/packages/next/src/server/render.tsx +++ b/packages/next/src/server/render.tsx @@ -15,7 +15,12 @@ import type { } from '../shared/lib/utils' import type { ImageConfigComplete } from '../shared/lib/image-config' import type { Redirect } from '../lib/load-custom-routes' -import type { NextApiRequestCookies, __ApiPreviewProps } from './api-utils' +import { + type NextApiRequestCookies, + type __ApiPreviewProps, + setLazyProp, +} from './api-utils' +import { getCookieParser } from './api-utils/get-cookie-parser' import type { FontManifest, FontConfig } from './font-utils' import type { LoadComponentsReturnType, ManifestItem } from './load-components' import type { @@ -386,6 +391,9 @@ export async function renderToHTMLImpl( renderOpts: Omit, extra: RenderOptsExtra ): Promise { + // Adds support for reading `cookies` in `getServerSideProps` when SSR. + setLazyProp({ req: req as any }, 'cookies', getCookieParser(req.headers)) + const renderResultMeta: RenderResultMetadata = {} // In dev we invalidate the cache by appending a timestamp to the resource URL. diff --git a/packages/next/src/shared/lib/i18n/get-locale-redirect.ts b/packages/next/src/shared/lib/i18n/get-locale-redirect.ts index 32e5fa7c94775..6016b8e2a0580 100644 --- a/packages/next/src/shared/lib/i18n/get-locale-redirect.ts +++ b/packages/next/src/shared/lib/i18n/get-locale-redirect.ts @@ -4,7 +4,7 @@ import { acceptLanguage } from '../../../server/accept-header' import { denormalizePagePath } from '../page-path/denormalize-page-path' import { detectDomainLocale } from './detect-domain-locale' import { formatUrl } from '../router/utils/format-url' -import { getCookieParser } from '../../../server/api-utils' +import { getCookieParser } from '../../../server/api-utils/get-cookie-parser' interface Options { defaultLocale: string diff --git a/packages/next/src/shared/lib/router/utils/prepare-destination.ts b/packages/next/src/shared/lib/router/utils/prepare-destination.ts index 6ebb5d5f898c9..2472388934da8 100644 --- a/packages/next/src/shared/lib/router/utils/prepare-destination.ts +++ b/packages/next/src/shared/lib/router/utils/prepare-destination.ts @@ -13,6 +13,7 @@ import { isInterceptionRouteAppPath, } from '../../../../server/future/helpers/interception-routes' import { NEXT_RSC_UNION_QUERY } from '../../../../client/components/app-router-headers' +import { getCookieParser } from '../../../../server/api-utils/get-cookie-parser' /** * Ensure only a-zA-Z are used for param names for proper interpolating @@ -64,7 +65,13 @@ export function matchHas( break } case 'cookie': { - value = (req as any).cookies[hasItem.key] + if ('cookies' in req) { + value = req.cookies[hasItem.key] + } else { + const cookies = getCookieParser(req.headers)() + value = cookies[hasItem.key] + } + break } case 'query': { diff --git a/test/integration/typescript/test/index.test.js b/test/integration/typescript/test/index.test.js index 5f59da2fc2ba7..95cca3ff5c413 100644 --- a/test/integration/typescript/test/index.test.js +++ b/test/integration/typescript/test/index.test.js @@ -21,8 +21,8 @@ const handleOutput = (msg) => { output += msg } -async function get$(path, query) { - const html = await renderViaHTTP(appPort, path, query) +async function get$(path, query, options) { + const html = await renderViaHTTP(appPort, path, query, options) return cheerio.load(html) } @@ -49,6 +49,19 @@ describe('TypeScript Features', () => { expect($('#cookies').text()).toBe('{}') }) + it('should render the cookies page with cookies', async () => { + const $ = await get$( + '/ssr/cookies', + {}, + { + headers: { + Cookie: 'key=value;', + }, + } + ) + expect($('#cookies').text()).toBe(`{"key":"value"}`) + }) + it('should render the generics page', async () => { const $ = await get$('/generics') expect($('#value').text()).toBe('Hello World from Generic')