opral / inlang-paraglide-js

Tree-shakable i18n library build on the inlang ecosystem.
https://inlang.com/m/gerre34r/library-inlang-paraglideJs
54 stars 1 forks source link

`getLanguage` breaks with Next.js 15 due to async request API breaking change #245

Closed asterikx closed 1 month ago

asterikx commented 1 month ago

Starting with latest RC of Next.js 15 (RC 2), the request APIs including headers() is async: https://nextjs.org/blog/next-15-rc2#async-request-apis-breaking-change

This change breaks the getLanguage() function from @inlang/paraglide-next (on the server):

https://github.com/opral/monorepo/blob/a0aaf620811b580e478a3d4add22e4c5475cbcc3/inlang/source-code/paraglide/paraglide-next/src/app/getLanguage.server.ts#L14

The getLanguage() function (and therefore every function that uses it) would have to be changed to an async function.

samuelstroschein commented 1 month ago

I feared that the Next 15 changes will impact the next adapter. Will have to wait for the metaframework overhaul project

mrthinger commented 1 month ago

I feared that the Next 15 changes will impact the next adapter. Will have to wait for the metaframework overhaul project

any idea on the eta for that?

samuelstroschein commented 1 month ago

Estimate is Q1 2025 for https://github.com/opral/inlang-paraglide-js/issues/217.

But I can invest time into reviewing, merging, and publishing a PR. Thus, if anyone wants to fix NextJS 15 support, please open a PR :)

asterikx commented 1 month ago

I did patch the package locally (seems to work fine). I can create a PR tomorrow.

iStorry commented 1 month ago

I did patch the package locally (seems to work fine). I can create a PR tomorrow.

I tried making some changes, but I'm encountering other errors for some reason.

async function getLanguage() {
    try {
        const allHeader = await headers();
        const langHeader = allHeader.get(PARAGLIDE_LANGUAGE_HEADER_NAME);
        ....
harrisrobin commented 1 month ago

would appreciate if you could share your fix on this @asterikx . I'm experiencing issues as well.

tconroy commented 1 month ago

+1 to above, running into this. would be great to push up as a branch we can install from in the meantime.

tconroy commented 1 month ago

I've opened a PR here: https://github.com/opral/monorepo/pull/3188

Unfortunately I wasn't able to run/test it locally, but perhaps it's a useful jumping off point to resolve this issue.

asterikx commented 1 month ago

Here's my patch file created using bun patch @inlang/paraglide-next: @inlang%2Fparaglide-next@0.6.0.patch

Seems to work just fine for me at the moment.

gerardmarquinarubio commented 1 month ago

Bumping for visibility

Here's my patch file created using bun patch @inlang/paraglide-next: @inlang%2Fparaglide-next@0.6.0.patch

Seems to work just fine for me at the moment.

Do you know how to use the patchfile from bun directly? Couldn't find any documentation.

tconroy commented 1 month ago

@gerardmarquinarubio add a patchedDependencies key to your package.json, with the value being an object that maps `"dependency@version": "patches/patchName.patch" file.

Something like this (typing on phone so apologies for typos):

{
  "patchedDependencies": {
    "@inlang/paraglide-next@0.6.0": "patches/@inlang/paraglide-next@0.6.0.patch"
  }
}
samuelstroschein commented 1 month ago

https://github.com/opral/monorepo/pull/3188 has been merged and published as v0.7. Thanks @tconroy for the PR!

CleanShot 2024-10-29 at 17 27 01@2x

gerardmarquinarubio commented 1 month ago

I'm getting Error: headers was called outside a request scope. Just as I updated to paraglide-next@latest.

Will try to give a repo to reproduce in a few minutes.

gerardmarquinarubio commented 1 month ago

@samuelstroschein @tconroy

Clone the repo, bun dev, you will get the error.

https://github.com/gerardmarquinarubio/paraglide-next-headers-bug

The minimal repo was as easy to reproduce as just creating a next-app using the CLI and defaults and initializing paraglide-next through the CLI.

samuelstroschein commented 1 month ago

@gerardmarquinarubio thanks for the repro. Highly appreciated if you can open a follow up pr to fix the issue

asterikx commented 1 month ago

I'm getting more errors:

  12 | async function getLanguage() {
  13 |     try {
> 14 |         const langHeader = await headers().get(PARAGLIDE_LANGUAGE_HEADER_NAME);
     |                                       ^
  15 |         const lang = isAvailableLanguageTag(langHeader) ? langHeader : sourceLanguageTag;
  16 |         return lang;
  17 |     }
 ⨯ unhandledRejection: Error: `headers` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context

Instead of:

 const langHeader = await headers().get(PARAGLIDE_LANGUAGE_HEADER_NAME);

it needs to be:

 const langHeader = (await headers()).get(PARAGLIDE_LANGUAGE_HEADER_NAME);
samuelstroschein commented 1 month ago

Yeah that makes sense. I'll fix it.

samuelstroschein commented 1 month ago

Released as 0.7.1

iStorry commented 4 weeks ago

Released as 0.7.1

Error: `headers` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context
    at getExpectedRequestStore (webpack-internal:///(shared)/./node_modules/next/dist/esm/server/app-render/work-unit-async-storage.external.js:26:11)
    at headers (webpack-internal:///(middleware)/./node_modules/next/dist/esm/server/request/headers.js:76:127)
    at getLanguage (webpack-internal:///(middleware)/./node_modules/@inlang/paraglide-next/dist/paraglide-next/src/app/getLanguage.server.js:21:88)
    at eval (webpack-internal:///(middleware)/./node_modules/@inlang/paraglide-next/dist/paraglide-next/src/app/providers/LanguageProvider.js:15:148)
    at eval (webpack-internal:///(middleware)/./node_modules/@inlang/paraglide-next/dist/paraglide-next/src/app/providers/LanguageProvider.js:16:3)
    at (middleware)/./node_modules/@inlang/paraglide-next/dist/paraglide-next/src/app/providers/LanguageProvider.js (/my-app/.next/server/src/middleware.js:258:1)
    at __webpack_require__ (my-app/.next/server/edge-runtime-webpack.js:37:33)
    at fn (my-app/.next/server/edge-runtime-webpack.js:281:21)
    at eval (webpack-internal:///(middleware)/./node_modules/@inlang/paraglide-next/dist/app/index.server.js:13:111)

The feature works well, but I keep getting the same problem when I build or use the cold start mode.

asterikx commented 4 weeks ago

Yes, I unfortunately keep getting the same error as well:

  12 | async function getLanguage() {
  13 |     try {
> 14 |         const langHeader = (await headers()).get(PARAGLIDE_LANGUAGE_HEADER_NAME);
     |                                        ^
  15 |         const lang = isAvailableLanguageTag(langHeader) ? langHeader : sourceLanguageTag;
  16 |         return lang;
  17 |     }
 ⨯ unhandledRejection: Error: `headers` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context
samuelstroschein commented 4 weeks ago

@asterikx did that happen with your patch file too?

if not, can you open a PR with the diff of your patch file?

asterikx commented 4 weeks ago

@samuelstroschein I did some more trial and error, and I could get rid of the error by:

  1. Removing/commenting out the top-level call to getLanguage in the LanguageProvider (I'm not sure about its implications):
    
    diff --git a/dist/paraglide-next/src/app/providers/LanguageProvider.js b/dist/paraglide-next/src/app/providers/LanguageProvider.js
    index 16cd65d717b250ec9644b5057b810f48cad0157d..dda9b90cbceb94ce909ca0f640a9d9315fd0f21c 100644
    --- a/dist/paraglide-next/src/app/providers/LanguageProvider.js
    +++ b/dist/paraglide-next/src/app/providers/LanguageProvider.js
    @@ -3,9 +3,9 @@ import { setLanguageTag, languageTag } from '$paraglide/runtime.js';
    import { ClientLanguageProvider } from './ClientLanguageProvider.js';
    import { getLanguage } from '../getLanguage.server.js';

-(async () => {

  1. Removing any calls to initializeLanguage in server actions as described in the docs here: https://inlang.com/m/osslbuzt/paraglide-next-i18n/advanced/usage-on-the-server. However, this forces me to pass the user's locale in any call to m functions, i.e. m.welcomeMessage(undefined, { languageTag: 'de-DE' }).

I'm curious, if there is a better way to handle server actions. Possibly need to call initializeLanguage in server action bodies instead of calling it top-level.

@samuelstroschein If you think it's okay to remove the top-level call to getLanguage in the LanguageProvider, I can open a PR for that.

tylkomat commented 4 weeks ago

I was just about the add a comment the moment you sent your comment @asterikx I was just doing your nr. 1 and that looks to be enough.

samuelstroschein commented 4 weeks ago

Okay, I was already surprised why the anonymous function was called in language provider. I will publish another patch hoping that it works.

It's unfortunate that i can't invest into an e2e nextjs 15 test atm to catch bugs before publishing a patch

samuelstroschein commented 4 weeks ago

Published as 0.7.2