Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: opennextjs/opennextjs-netlify
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v5.10.0
Choose a base ref
...
head repository: opennextjs/opennextjs-netlify
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v5.10.1
Choose a head ref
  • 3 commits
  • 8 files changed
  • 2 contributors

Commits on Mar 17, 2025

  1. fix: adjust bundling to not produce duplicate inlined internal modules (

    #2774)
    
    * chore: add some validation to bundling to ensure we don't produce duplicate/inling of regional-blob-store module in unexptected built modules
    
    * chore: don't hardcode repo directory in build script
    
    * test: adjust assertion for case where multiple cache-status values are being comma separated and not line separated
    
    * Update tools/build.js
    
    Co-authored-by: Eduardo Bouças <mail@eduardoboucas.com>
    
    ---------
    
    Co-authored-by: Eduardo Bouças <mail@eduardoboucas.com>
    pieh and eduardoboucas authored Mar 17, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    5d65c92 View commit details
  2. Copy the full SHA
    58cafc1 View commit details

Commits on Mar 19, 2025

  1. Copy the full SHA
    8675b3e View commit details
Showing with 105 additions and 11 deletions.
  1. +1 −1 .release-please-manifest.json
  2. +7 −0 CHANGELOG.md
  3. +2 −2 package-lock.json
  4. +1 −1 package.json
  5. +3 −0 src/run/handlers/server.ts
  6. +53 −2 src/run/regional-blob-store.cts
  7. +2 −2 tests/e2e/simple-app.test.ts
  8. +36 −3 tools/build.js
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "5.10.0"
".": "5.10.1"
}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [5.10.1](https://github.com/opennextjs/opennextjs-netlify/compare/v5.10.0...v5.10.1) (2025-03-17)


### Bug Fixes

* add more measures to prevent using data-cache for blob operations ([#2775](https://github.com/opennextjs/opennextjs-netlify/issues/2775)) ([58cafc1](https://github.com/opennextjs/opennextjs-netlify/commit/58cafc152ec70539122b87e46578abd75dc9daa8))

## [5.10.0](https://github.com/opennextjs/opennextjs-netlify/compare/v5.9.4...v5.10.0) (2025-03-07)


4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@netlify/plugin-nextjs",
"version": "5.10.0",
"version": "5.10.1",
"description": "Run Next.js seamlessly on Netlify",
"main": "./dist/index.js",
"type": "module",
3 changes: 3 additions & 0 deletions src/run/handlers/server.ts
Original file line number Diff line number Diff line change
@@ -13,12 +13,15 @@ import {
setCacheTagsHeaders,
setVaryHeaders,
} from '../headers.js'
import { setFetchBeforeNextPatchedIt } from '../regional-blob-store.cjs'
import { nextResponseProxy } from '../revalidate.js'

import { getLogger, type RequestContext } from './request-context.cjs'
import { getTracer } from './tracer.cjs'
import { setupWaitUntil } from './wait-until.cjs'

setFetchBeforeNextPatchedIt(globalThis.fetch)

const nextImportPromise = import('../next.cjs')

setupWaitUntil()
55 changes: 53 additions & 2 deletions src/run/regional-blob-store.cts
Original file line number Diff line number Diff line change
@@ -1,11 +1,62 @@
import { getDeployStore, GetWithMetadataOptions, Store } from '@netlify/blobs'

const fetchBeforeNextPatchedIt = globalThis.fetch
const FETCH_BEFORE_NEXT_PATCHED_IT = Symbol.for('nf-not-patched-fetch')
const extendedGlobalThis = globalThis as typeof globalThis & {
[FETCH_BEFORE_NEXT_PATCHED_IT]?: typeof globalThis.fetch
}

/**
* Attempt to extract original fetch in case it was patched by Next.js already
*
* @see github.com/vercel/next.js/blob/fa214c74c1d8023098c0e94e57f917ef9f1afd1a/packages/next/src/server/lib/patch-fetch.ts#L986
*/
function attemptToGetOriginalFetch(
fetch: typeof globalThis.fetch & {
_nextOriginalFetch?: typeof globalThis.fetch
},
) {
return fetch._nextOriginalFetch ?? fetch
}

function forceOptOutOfUsingDataCache(fetch: typeof globalThis.fetch): typeof globalThis.fetch {
return (input, init) => {
return fetch(input, {
...init,
next: {
...init?.next,
// setting next.internal = true should prevent from trying to use data cache
// https://github.com/vercel/next.js/blob/fa214c74c1d8023098c0e94e57f917ef9f1afd1a/packages/next/src/server/lib/patch-fetch.ts#L174
// https://github.com/vercel/next.js/blob/fa214c74c1d8023098c0e94e57f917ef9f1afd1a/packages/next/src/server/lib/patch-fetch.ts#L210-L213
// this is last line of defense in case we didn't manage to get unpatched fetch that will not affect
// fetch if it's unpatched so it should be safe to apply always if we aren't sure if we use patched fetch

// @ts-expect-error - this is an internal field that Next.js doesn't add to its global
// type overrides for RequestInit type (like `next.revalidate` or `next.tags`)
internal: true,
},
})
}
}

export const setFetchBeforeNextPatchedIt = (fetch: typeof globalThis.fetch) => {
// we store in globalThis in case we have multiple copies of this module
// just as precaution

extendedGlobalThis[FETCH_BEFORE_NEXT_PATCHED_IT] = forceOptOutOfUsingDataCache(
attemptToGetOriginalFetch(fetch),
)
}

const fetchBeforeNextPatchedItFallback = forceOptOutOfUsingDataCache(
attemptToGetOriginalFetch(globalThis.fetch),
)
const getFetchBeforeNextPatchedIt = () =>
extendedGlobalThis[FETCH_BEFORE_NEXT_PATCHED_IT] ?? fetchBeforeNextPatchedItFallback

export const getRegionalBlobStore = (args: GetWithMetadataOptions = {}): Store => {
return getDeployStore({
...args,
fetch: fetchBeforeNextPatchedIt,
fetch: getFetchBeforeNextPatchedIt(),
region: process.env.USE_REGIONAL_BLOBS?.toUpperCase() === 'TRUE' ? undefined : 'us-east-2',
})
}
4 changes: 2 additions & 2 deletions tests/e2e/simple-app.test.ts
Original file line number Diff line number Diff line change
@@ -12,8 +12,8 @@ test('Renders the Home page correctly', async ({ page, simple }) => {

await expect(page).toHaveTitle('Simple Next App')

expect(headers['cache-status']).toMatch(/^"Next.js"; hit$/m)
expect(headers['cache-status']).toMatch(/^"Netlify Edge"; fwd=miss$/m)
expect(headers['cache-status'].replaceAll(', ', '\n')).toMatch(/^"Next.js"; hit$/m)
expect(headers['cache-status'].replaceAll(', ', '\n')).toMatch(/^"Netlify Edge"; fwd=miss$/m)
// "Netlify Durable" assertion is skipped because we are asserting index page and there are possible that something else is making similar request to it
// and as a result we can see many possible statuses for it: `fwd=miss`, `fwd=miss; stored`, `hit; ttl=<ttl>` so there is no point in asserting on that
// "Netlify Edge" status suffers from similar issue, but is less likely to manifest (only if those requests would be handled by same CDN node) and retries
39 changes: 36 additions & 3 deletions tools/build.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createWriteStream } from 'node:fs'
import { cp, rm } from 'node:fs/promises'
import { join, resolve } from 'node:path'
import { cp, readFile, rm } from 'node:fs/promises'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { Readable } from 'stream'
import { finished } from 'stream/promises'

@@ -11,6 +12,8 @@ import glob from 'fast-glob'
const OUT_DIR = 'dist'
await rm(OUT_DIR, { force: true, recursive: true })

const repoDirectory = dirname(resolve(fileURLToPath(import.meta.url), '..'))

const entryPointsESM = await glob('src/**/*.ts', { ignore: ['**/*.test.ts'] })
const entryPointsCJS = await glob('src/**/*.cts')

@@ -39,7 +42,7 @@ async function bundle(entryPoints, format, watch) {
name: 'mark-runtime-modules-as-external',
setup(pluginBuild) {
pluginBuild.onResolve({ filter: /^\..*\.c?js$/ }, (args) => {
if (args.importer.includes(join('opennextjs-netlify', 'src'))) {
if (args.importer.includes(join(repoDirectory, 'src'))) {
return { path: args.path, external: true }
}
})
@@ -126,8 +129,38 @@ await Promise.all([
cp('src/build/templates', join(OUT_DIR, 'build/templates'), { recursive: true, force: true }),
])

async function ensureNoRegionalBlobsModuleDuplicates() {
const REGIONAL_BLOB_STORE_CONTENT_TO_FIND = 'fetchBeforeNextPatchedIt'

const filesToTest = await glob(`${OUT_DIR}/**/*.{js,cjs}`)
const unexpectedModulesContainingFetchBeforeNextPatchedIt = []
let foundInExpectedModule = false
for (const fileToTest of filesToTest) {
const content = await readFile(fileToTest, 'utf-8')
if (content.includes(REGIONAL_BLOB_STORE_CONTENT_TO_FIND)) {
if (fileToTest.endsWith('run/regional-blob-store.cjs')) {
foundInExpectedModule = true
} else {
unexpectedModulesContainingFetchBeforeNextPatchedIt.push(fileToTest)
}
}
}
if (!foundInExpectedModule) {
throw new Error(
'Expected to find "fetchBeforeNextPatchedIt" variable in "run/regional-blob-store.cjs", but it was not found. This might indicate a setup change that requires the bundling validation in "tools/build.js" to be adjusted.',
)
}
if (unexpectedModulesContainingFetchBeforeNextPatchedIt.length !== 0) {
throw new Error(
`Bundling produced unexpected duplicates of "regional-blob-store" module in following built modules:\n${unexpectedModulesContainingFetchBeforeNextPatchedIt.map((filePath) => ` - ${filePath}`).join('\n')}`,
)
}
}

if (watch) {
console.log('Starting compilation in watch mode...')
} else {
await ensureNoRegionalBlobsModuleDuplicates()

console.log('Finished building 🎉')
}