Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@ export async function createComponentStylesAndScripts({

const styles = cssHrefs
? cssHrefs.map((href, index) => {
// In dev, Safari and Firefox will cache the resource during HMR:
// - https://github.com/vercel/next.js/issues/5860
// - https://bugs.webkit.org/show_bug.cgi?id=187726
// Because of this, we add a `?v=` query to bypass the cache during
// development. We need to also make sure that the number is always
// increasing.
const fullHref = `${ctx.assetPrefix}/_next/${href}${getAssetQueryString(
ctx,
true
Expand Down
9 changes: 7 additions & 2 deletions packages/next/src/server/app-render/get-asset-query-string.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import type { AppRenderContext } from './app-render'

const isDev = process.env.NODE_ENV === 'development'
const isTurbopack = !!process.env.TURBOPACK
export function getAssetQueryString(
ctx: AppRenderContext,
addTimestamp: boolean
) {
const isDev = process.env.NODE_ENV === 'development'
let qs = ''

if (isDev && addTimestamp) {
// In development we add the request timestamp to allow react to
// reload assets when a new RSC response is received.
// Turbopack handles HMR of assets itself and react doesn't need to reload them
// so this approach is not needed for Turbopack.
if (isDev && !isTurbopack && addTimestamp) {
qs += `?v=${ctx.requestTimestamp}`
}

Expand Down
5 changes: 4 additions & 1 deletion packages/next/src/server/app-render/get-layer-assets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ export function getLayerAssets({

const scripts = scriptTags
? scriptTags.map((href, index) => {
const fullSrc = `${ctx.assetPrefix}/_next/${href}`
const fullSrc = `${ctx.assetPrefix}/_next/${href}${getAssetQueryString(
ctx,
true
)}`

return <script src={fullSrc} async={true} key={`script-${index}`} />
})
Expand Down
20 changes: 14 additions & 6 deletions packages/next/src/server/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -416,12 +416,20 @@ export async function renderToHTMLImpl(

const metadata: PagesRenderResultMetadata = {}

// In dev we invalidate the cache by appending a timestamp to the resource URL.
// This is a workaround to fix https://github.com/vercel/next.js/issues/5860
// TODO: remove this workaround when https://bugs.webkit.org/show_bug.cgi?id=187726 is fixed.
metadata.assetQueryString = renderOpts.dev
? renderOpts.assetQueryString || `?ts=${Date.now()}`
: ''
metadata.assetQueryString =
(renderOpts.dev && renderOpts.assetQueryString) || ''

if (renderOpts.dev && !metadata.assetQueryString) {
const userAgent = (req.headers['user-agent'] || '').toLowerCase()
if (userAgent.includes('safari') && !userAgent.includes('chrome')) {
// In dev we invalidate the cache by appending a timestamp to the resource URL.
// This is a workaround to fix https://github.com/vercel/next.js/issues/5860
// TODO: remove this workaround when https://bugs.webkit.org/show_bug.cgi?id=187726 is fixed.
// Note: The workaround breaks breakpoints on reload since the script url always changes,
// so we only apply it to Safari.
Comment on lines +428 to +429
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

metadata.assetQueryString = `?ts=${Date.now()}`
}
}

// if deploymentId is provided we append it to all asset requests
if (renderOpts.deploymentId) {
Expand Down
10 changes: 8 additions & 2 deletions test/integration/app-document/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,14 @@ describe('Document and App', () => {
rendering(
context,
'Rendering via HTTP',
(p, q) => renderViaHTTP(context.appPort, p, q),
(p, q) => fetchViaHTTP(context.appPort, p, q)
(p, q) =>
renderViaHTTP(context.appPort, p, q, {
headers: { 'user-agent': 'Safari' },
}),
(p, q) =>
fetchViaHTTP(context.appPort, p, q, {
headers: { 'user-agent': 'Safari' },
})
)
client(context, (p, q) => renderViaHTTP(context.appPort, p, q))
csp(context, (p, q) => renderViaHTTP(context.appPort, p, q))
Expand Down