Describe the bug
The languageTag is defined in module scope in the auto generated runtime.js file. On the client this is fine because the client is the only one that's managing it. On the server there will be an issue if there's concurrent requests when using with Remix's defer because renderToPipeableStream is not synchronous.
function handleBrowserRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const lang = getContextLang(remixContext, {
defaultValue: availableLanguageTags[0],
availableLanguages: availableLanguageTags,
urlParam: 'lang',
});
setLanguageTag(lang); // <-------- This is the issue
...
Suppose an English user visits the app, Remix will render the HTML until it hits a suspense boundary and will render a fall back in its place. Remix will continue to render the Suspense boundary's children only when the promise is resolved. Now suppose a Japanese user vists the app at the same time. This will cause Remix to change the language for the English user to Japanese and cause client / server mismatch
To Reproduce
As an example, suppose we have ($lang).ssr-links.tsx with a deferred loader supporting two languages (en, ja). The promise for languages that's not Japanese are configured to be delayed for 5s
routes/($lang).ssr-links.tsx
import { Await, useLoaderData } from "@remix-run/react";
import * as m from "../paraglide/messages";
import { LoaderFunctionArgs, defer } from "@remix-run/node";
import { Suspense } from "react";
export let loader = async ({ params }: LoaderFunctionArgs) => {
return defer({
lang: new Promise<string>((resolve) => {
// Resolve immediately for Japanese
if (params.lang === "ja") {
resolve("ja");
} else {
// Resolve the promise in 5s for other languages
setTimeout(() => {
if (typeof params.lang === "string") { // Make Typescript happy
resolve(params.lang);
}
}, 5000);
}
}),
});
};
export default function Index() {
const data = useLoaderData<typeof loader>();
return (
<div
style={{
fontFamily: "system-ui, sans-serif",
lineHeight: "1.8",
backgroundColor: "orange",
}}
>
<h1>{m.title()}</h1>
<p>{m.description()}</p>
<Suspense fallback={<h1>Fall back</h1>}>
<Await resolve={data.lang}>
{(lang) => (
<h1>
{lang} {m.encouragement()}
</h1>
)}
</Await>
</Suspense>
</div>
);
}
messages/en.json
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"title": "Remix web example",
"description": "This is a simple example of how to use Remix to create a web app.",
"encouragement": "You're doing great! Keep it up!"
}
messages/ja.json
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"title": "リミックスウェブの例",
"description": "これは、Remix を使用して Web アプリを作成する方法の簡単な例です。",
"encouragement": "素晴らしいですね!これからも頑張ってください!"
}
Setup
- Set up paraglide js with
en and ja
- Copy over translations above to the
messages directory
- Create
routes/($lang).ssr-links.tsx and copy the loader above into the file
- Open http://localhost:5173/en/ssr-links in one tab
- Open http://localhost:5173/ja/ssr-links in another tab
Producing the bug
- Refresh http://localhost:5173/en/ssr-links
- Immediately after, refresh http://localhost:5173/ja/ssr-links
- Head back to the http://localhost:5173/en/ssr-links tab and check console
Expected behavior
Expect the server render to always use the language from the request that triggered it
Screenshots
Screen shots showing page source and console for the English tab

Desktop (please complete the following information):
- Remix version: [e.g. 2.8.1]
- Paraglide version: [e.g. 1.4.0]
- Vite version: [e.g. 5.1.0]
Describe the bug
The
languageTagis defined in module scope in the auto generatedruntime.jsfile. On the client this is fine because the client is the only one that's managing it. On the server there will be an issue if there's concurrent requests when using with Remix'sdeferbecauserenderToPipeableStreamis not synchronous.Suppose an English user visits the app, Remix will render the HTML until it hits a suspense boundary and will render a fall back in its place. Remix will continue to render the Suspense boundary's children only when the promise is resolved. Now suppose a Japanese user vists the app at the same time. This will cause Remix to change the language for the English user to Japanese and cause client / server mismatch
To Reproduce
As an example, suppose we have
($lang).ssr-links.tsxwith a deferred loader supporting two languages (en, ja). The promise for languages that's not Japanese are configured to be delayed for 5sroutes/($lang).ssr-links.tsxmessages/en.json{ "$schema": "https://inlang.com/schema/inlang-message-format", "title": "Remix web example", "description": "This is a simple example of how to use Remix to create a web app.", "encouragement": "You're doing great! Keep it up!" }messages/ja.json{ "$schema": "https://inlang.com/schema/inlang-message-format", "title": "リミックスウェブの例", "description": "これは、Remix を使用して Web アプリを作成する方法の簡単な例です。", "encouragement": "素晴らしいですね!これからも頑張ってください!" }Setup
enandjamessagesdirectoryroutes/($lang).ssr-links.tsxand copy the loader above into the fileProducing the bug
Expected behavior
Expect the server render to always use the language from the request that triggered it
Screenshots

Screen shots showing page source and console for the English tab
Desktop (please complete the following information):