Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: conditional cookies in draft mode #50660

3 changes: 2 additions & 1 deletion packages/next/src/client/components/draft-mode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DraftModeProvider } from '../../server/async-storage/draft-mode-provider'
import type { DraftModeProvider } from '../../server/async-storage/draft-mode-provider'

import { staticGenerationBailout } from './static-generation-bailout'

export class DraftMode {
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/app-render/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export type RenderOptsPartial = {
nextConfigOutput?: 'standalone' | 'export'
appDirDevErrorLogger?: (err: any) => Promise<void>
originalPathname?: string
isDraftMode?: boolean
deploymentId?: string
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type StaticGenerationContext = {
isBot?: boolean
nextExport?: boolean
fetchCache?: StaticGenerationStore['fetchCache']
isDraftMode?: boolean

/**
* A hack around accessing the store value outside the context of the
Expand Down Expand Up @@ -46,11 +47,15 @@ export const StaticGenerationAsyncStorageWrapper: AsyncStorageWrapper<
* or throw an error. It is the sole responsibility of the caller to
* ensure they aren't e.g. requesting dynamic HTML for an AMP page.
*
* 3.) If the request is in draft mode, we must generate dynamic HTML.
*
* These rules help ensure that other existing features like request caching,
* coalescing, and ISR continue working as intended.
*/
const isStaticGeneration =
!renderOpts.supportsDynamicHTML && !renderOpts.isBot
!renderOpts.supportsDynamicHTML &&
!renderOpts.isBot &&
!renderOpts.isDraftMode

const store: StaticGenerationStore = {
isStaticGeneration,
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1744,6 +1744,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {

supportsDynamicHTML,
isOnDemandRevalidate,
isDraftMode: isPreviewMode,
}

const renderResult = await this.renderHTML(
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export type RenderOptsPartial = {
largePageDataBytes?: number
isOnDemandRevalidate?: boolean
strictNextHead: boolean
isDraftMode?: boolean
deploymentId?: string
}

Expand Down
25 changes: 25 additions & 0 deletions test/e2e/app-dir/draft-mode/app/with-cookies/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import { cookies, draftMode } from 'next/headers'

export default function Page() {
const { isEnabled } = draftMode()
let data: string | undefined
if (isEnabled) {
data = cookies().get('data')?.value
}

return (
<>
<h1>Draft Mode with dynamic cookie</h1>
<p>
Random: <em id="rand">{Math.random()}</em>
</p>
<p>
State: <strong id="mode">{isEnabled ? 'ENABLED' : 'DISABLED'}</strong>
</p>
<p>
Data: <em id="data">{data}</em>
</p>
</>
)
}
35 changes: 30 additions & 5 deletions test/e2e/app-dir/draft-mode/draft-mode-node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,38 @@ createNextDescribe(
files: __dirname,
},
({ next, isNextDev }) => {
let initialRand = 'unintialized'
let origRandHome = 'unintialized'
let origRandWithCookies = 'unintialized'
let Cookie = ''

it('should use initial rand when draft mode is disabled', async () => {
it('should use initial rand when draft mode is disabled on /index', async () => {
const $ = await next.render$('/')
expect($('#mode').text()).toBe('DISABLED')
expect($('#rand').text()).toBeDefined()
initialRand = $('#rand').text()
origRandHome = $('#rand').text()
})

it('should use initial rand when draft mode is disabled on /with-cookies', async () => {
const $ = await next.render$('/with-cookies')
expect($('#mode').text()).toBe('DISABLED')
expect($('#rand').text()).toBeDefined()
expect($('#data').text()).toBe('')
origRandWithCookies = $('#rand').text()
})

if (!isNextDev) {
it('should not generate rand when draft mode disabled during next start', async () => {
const $ = await next.render$('/')
expect($('#mode').text()).toBe('DISABLED')
expect($('#rand').text()).toBe(initialRand)
expect($('#rand').text()).toBe(origRandHome)
})

it('should not read other cookies when draft mode disabled during next start', async () => {
const opts = { headers: { Cookie: `data=cool` } }
const $ = await next.render$('/with-cookies', {}, opts)
expect($('#mode').text()).toBe('DISABLED')
expect($('#rand').text()).toBe(origRandWithCookies)
expect($('#data').text()).toBe('')
})
}

Expand Down Expand Up @@ -51,7 +68,15 @@ createNextDescribe(
const opts = { headers: { Cookie } }
const $ = await next.render$('/', {}, opts)
expect($('#mode').text()).toBe('ENABLED')
expect($('#rand').text()).not.toBe(initialRand)
expect($('#rand').text()).not.toBe(origRandHome)
})

it('should read other cookies when draft mode enabled', async () => {
const opts = { headers: { Cookie: `${Cookie};data=cool` } }
const $ = await next.render$('/with-cookies', {}, opts)
expect($('#mode').text()).toBe('ENABLED')
expect($('#rand').text()).not.toBe(origRandWithCookies)
expect($('#data').text()).toBe('cool')
})

it('should be enabled from api route handler when draft mode enabled', async () => {
Expand Down