diff --git a/cypress/e2e/default/i18n.cy.ts b/cypress/e2e/default/i18n.cy.ts index ffe5291943..dc223c4d1d 100644 --- a/cypress/e2e/default/i18n.cy.ts +++ b/cypress/e2e/default/i18n.cy.ts @@ -35,4 +35,34 @@ describe('Localization', () => { cy.findByText('The current locale is en') }) + + it(`function handler doesn't produce locale when first language in 'Accept-Language' header is not matching any of locales`, () => { + cy.request({ + url: `/`, + followRedirect: false, + headers: { + // jp is not in i18n config, fr is in config + // Netlify Redirects only match on first language + // https://docs.netlify.com/routing/redirects/redirect-options/#redirect-by-country-or-language + // > Language-based redirects always match against the first language reported by the browser in the Accept-Language header regardless of quality value weighting. + // while Next.js matches on every language: https://github.com/vercel/next.js/blob/5d9597879c46b383d595d6f7b37fd373325b7544/test/unit/accept-headers.test.ts + 'Accept-Language': 'jp, fr;q=0.9', + // make sure we don't use cached results + cookie: '__prerender_bypass=1', + // above cookie header cause us to hit non-ODB function variant + // below header allow us to force ODB-only code path despite not running ODB (this is just for testing purposes) + 'x-next-just-first-accept-language': '1', + }, + }).then((response) => { + // make sure we didn't hit SSR handler - not ODB and nothing else handles this request + // once platform starts supporting more languages in Accept-Language header - this test + // will start failing because then we will get platform level redirect + expect(response.headers['x-nf-render-mode']).to.eq('ssr') + + // we don't expect for function handler to respond with a redirect + // locale redirects should be handled by Netlify redirects + // otherwise we could wrongly cache locale redirect generated by ODB + expect(response.status).to.eq(200) + }) + }) }) \ No newline at end of file diff --git a/packages/runtime/src/templates/getHandler.ts b/packages/runtime/src/templates/getHandler.ts index 176ddce22c..49b35cfadb 100644 --- a/packages/runtime/src/templates/getHandler.ts +++ b/packages/runtime/src/templates/getHandler.ts @@ -130,6 +130,16 @@ const makeHandler = ({ conf, app, pageRoot, NextServer, staticManifest = [], mod const query = new URLSearchParams(event.queryStringParameters).toString() event.path = query ? `${event.path}?${query}` : event.path + if (event.headers['accept-language'] && (mode === 'odb' || event.headers['x-next-just-first-accept-language'])) { + // keep just first language to match Netlify redirect limitation: + // https://docs.netlify.com/routing/redirects/redirect-options/#redirect-by-country-or-language + // > Language-based redirects always match against the first language reported by the browser in the Accept-Language header regardless of quality value weighting. + // If we wouldn't keep just first language, it's possible for `next-server` to generate locale redirect that could be cached by ODB + // because it matches on every language listed: https://github.com/vercel/next.js/blob/5d9597879c46b383d595d6f7b37fd373325b7544/test/unit/accept-headers.test.ts + // 'x-next-just-first-accept-language' header is escape hatch to be able to hit this code for tests (both automated and manual) + event.headers['accept-language'] = event.headers['accept-language'].replace(/\s*,.*$/, '') + } + const { headers, ...result } = await getBridge(event, context).launcher(event, context) // Convert all headers to multiValueHeaders