T
TanStack12h ago
quickest-silver

Run code before SSR and CSR in RC

I'm upgrading to RC from 1.120. Prior RC, I had i18n setup with lingui similar to https://github.com/lingui/js-lingui/tree/main/examples/tanstack-start
// ssr.ts
/// <reference types="vinxi/types/server" />
import { i18n } from "@lingui/core"
import {
createStartHandler,
defaultStreamHandler,
defineEventHandler,
} from "@tanstack/react-start/server"
import { getRouterManifest } from "@tanstack/react-start/router-manifest"

import { createRouter } from "./router"
import { setupLocaleFromRequest } from "./modules/lingui/i18n.server"

export default defineEventHandler(async (event) => {
await setupLocaleFromRequest()

return createStartHandler({
createRouter: () => {
return createRouter({ i18n })
},
getRouterManifest,
})(defaultStreamHandler)(event)
})
// ssr.ts
/// <reference types="vinxi/types/server" />
import { i18n } from "@lingui/core"
import {
createStartHandler,
defaultStreamHandler,
defineEventHandler,
} from "@tanstack/react-start/server"
import { getRouterManifest } from "@tanstack/react-start/router-manifest"

import { createRouter } from "./router"
import { setupLocaleFromRequest } from "./modules/lingui/i18n.server"

export default defineEventHandler(async (event) => {
await setupLocaleFromRequest()

return createStartHandler({
createRouter: () => {
return createRouter({ i18n })
},
getRouterManifest,
})(defaultStreamHandler)(event)
})
// __root.tsx
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang={i18n.locale}>
// __root.tsx
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang={i18n.locale}>
// client.ts
/// <reference types="vinxi/types/client" />
import { i18n } from "@lingui/core"
import { hydrateRoot } from "react-dom/client"
import { StartClient } from "@tanstack/react-start"
import { dynamicActivate } from "./modules/lingui/i18n"

import { createRouter } from "./router"

// The lang should be set by the server
dynamicActivate(document.documentElement.lang)

const router = createRouter({ i18n })

hydrateRoot(document, <StartClient router={router} />)
// client.ts
/// <reference types="vinxi/types/client" />
import { i18n } from "@lingui/core"
import { hydrateRoot } from "react-dom/client"
import { StartClient } from "@tanstack/react-start"
import { dynamicActivate } from "./modules/lingui/i18n"

import { createRouter } from "./router"

// The lang should be set by the server
dynamicActivate(document.documentElement.lang)

const router = createRouter({ i18n })

hydrateRoot(document, <StartClient router={router} />)
In RC, client.ts is the same but I don't know how to inject it to server.ts The problem I want to solve is to call dynamicActivate (which internally imports messages and sets the active locale) before SSR and CSR I considered global middleware at start.ts (1/2)
GitHub
js-lingui/examples/tanstack-start at main · lingui/js-lingui
🌍 📖 A readable, automated, and optimized (2 kb) internationalization for JavaScript - lingui/js-lingui
12 Replies
quickest-silver
quickest-silverOP12h ago
// start.ts
import { createMiddleware, createStart } from "@tanstack/react-start";
import { dynamicActivate } from "./i18n/i18n";
import { getLocaleFromRequest } from "./i18n/server";

const localeMiddleware = createMiddleware().server(
async ({ request, next }) => {
await dynamicActivate(getLocaleFromRequest(request));
return next();
}
);

export const startInstance = createStart(() => {
return {
requestMiddleware: [localeMiddleware],
};
});
// start.ts
import { createMiddleware, createStart } from "@tanstack/react-start";
import { dynamicActivate } from "./i18n/i18n";
import { getLocaleFromRequest } from "./i18n/server";

const localeMiddleware = createMiddleware().server(
async ({ request, next }) => {
await dynamicActivate(getLocaleFromRequest(request));
return next();
}
);

export const startInstance = createStart(() => {
return {
requestMiddleware: [localeMiddleware],
};
});
But the docs stats:
Global request middleware runs before every request, including both server routes and server functions.
I've no idea whether SSR is included by this or not.
blank-aquamarine
blank-aquamarine12h ago
yes it is docs need updating here
quickest-silver
quickest-silverOP12h ago
the global middleware is correct? And is there a better way to await an async (import messages) before CSR?
blank-aquamarine
blank-aquamarine12h ago
better than what?
quickest-silver
quickest-silverOP12h ago
// client.ts
import { StartClient } from "@tanstack/react-start/client";
import { hydrateRoot } from "react-dom/client";
import type { Lang } from "@/i18n/i18n";
import { dynamicActivate } from "./i18n/i18n";

void dynamicActivate(document.documentElement.lang as Lang);

hydrateRoot(document, <StartClient />);
// client.ts
import { StartClient } from "@tanstack/react-start/client";
import { hydrateRoot } from "react-dom/client";
import type { Lang } from "@/i18n/i18n";
import { dynamicActivate } from "./i18n/i18n";

void dynamicActivate(document.documentElement.lang as Lang);

hydrateRoot(document, <StartClient />);
blank-aquamarine
blank-aquamarine12h ago
so you can either do this - in getRouter (by making getRouter an isomorphic function via createIsomorphicFn) - or in hydrate (function passed into createRouter())
quickest-silver
quickest-silverOP12h ago
Could getRouter be async? Is hydrate called before or after hydration? I’m asking because I read the locale from window.documentElement.lang. The same question applies to the isomorphic fn in getRouter: how can I pass the locale from the server (extracted from the request) to the client and then use it in dynamicActivate?
blank-aquamarine
blank-aquamarine12h ago
yes getRouter can be async hydrate is called before react hydration happens (or rather, react hydration suspends as long as router is still initializing)
The same question applies to the isomorphic fn in getRouter: how can I pass the locale from the server (extracted from the request) to the client and then use it in dynamicActivate?
so you dont want to read it of window then on the client?
quickest-silver
quickest-silverOP12h ago
I read it from window because I set <html lang={serverLocale}> on the server, but If hydrate is called before react hydration, will the html attribute be available on window? sorry I got confused react hydration and browser loading JS. the dom attribute will be available on window.documentElement.lang before even react hydration. Thank you so much for your help Manuel
blank-aquamarine
blank-aquamarine12h ago
sure! you can still send it to the client btw you can return an object from dehydrate (runs on the server) and then this will be passed into hydrate
quickest-silver
quickest-silverOP9h ago
the api looks so cool
const router = createTanStackRouter({
dehydrate: () => ({ lang: getLocaleFromRequest() }),
hydrate: (dehydrated) => dynamicActivate(dehydrated.lang),
const router = createTanStackRouter({
dehydrate: () => ({ lang: getLocaleFromRequest() }),
hydrate: (dehydrated) => dynamicActivate(dehydrated.lang),
Except it does this
✓ 3166 modules transformed.
✗ Build failed in 7.93s
error during build:
[vite]: Rollup failed to resolve import "tanstack-start-injected-head-scripts:v" from "/home/ahmed/projects/63-basmah/node_modules/@tanstack/start-server-core/dist/esm/loadVirtualModule.js".
This is most likely unintended because it can break your application at runtime.
If you do want to externalize this module explicitly add it to
`build.rollupOptions.external`
at viteLog (file:///home/ahmed/projects/63-basmah/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:33989:57)
at file:///home/ahmed/projects/63-basmah/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:34025:73
at onwarn (file:///home/ahmed/projects/63-basmah/node_modules/@vitejs/plugin-react/dist/index.mjs:104:9)
at file:///home/ahmed/projects/63-basmah/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:34025:28
at onRollupLog (file:///home/ahmed/projects/63-basmah/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:34020:63)
at onLog (file:///home/ahmed/projects/63-basmah/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:33821:4)
at file:///home/ahmed/projects/63-basmah/node_modules/rollup/dist/es/shared/node-entry.js:20937:32
at Object.logger [as onLog] (file:///home/ahmed/projects/63-basmah/node_modules/rollup/dist/es/shared/node-entry.js:22823:9)
at ModuleLoader.handleInvalidResolvedId (file:///home/ahmed/projects/63-basmah/node_modules/rollup/dist/es/shared/node-entry.js:21567:26)
at ModuleLoader.resolveDynamicImport (file:///home/ahmed/projects/63-basmah/node_modules/rollup/dist/es/shared/node-entry.js:21625:58)
at async file:///home/ahmed/projects/63-basmah/node_modules/rollup/dist/es/shared/node-entry.js:21509:32
 ELIFECYCLE  Command failed with exit code 1.
✓ 3166 modules transformed.
✗ Build failed in 7.93s
error during build:
[vite]: Rollup failed to resolve import "tanstack-start-injected-head-scripts:v" from "/home/ahmed/projects/63-basmah/node_modules/@tanstack/start-server-core/dist/esm/loadVirtualModule.js".
This is most likely unintended because it can break your application at runtime.
If you do want to externalize this module explicitly add it to
`build.rollupOptions.external`
at viteLog (file:///home/ahmed/projects/63-basmah/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:33989:57)
at file:///home/ahmed/projects/63-basmah/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:34025:73
at onwarn (file:///home/ahmed/projects/63-basmah/node_modules/@vitejs/plugin-react/dist/index.mjs:104:9)
at file:///home/ahmed/projects/63-basmah/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:34025:28
at onRollupLog (file:///home/ahmed/projects/63-basmah/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:34020:63)
at onLog (file:///home/ahmed/projects/63-basmah/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:33821:4)
at file:///home/ahmed/projects/63-basmah/node_modules/rollup/dist/es/shared/node-entry.js:20937:32
at Object.logger [as onLog] (file:///home/ahmed/projects/63-basmah/node_modules/rollup/dist/es/shared/node-entry.js:22823:9)
at ModuleLoader.handleInvalidResolvedId (file:///home/ahmed/projects/63-basmah/node_modules/rollup/dist/es/shared/node-entry.js:21567:26)
at ModuleLoader.resolveDynamicImport (file:///home/ahmed/projects/63-basmah/node_modules/rollup/dist/es/shared/node-entry.js:21625:58)
at async file:///home/ahmed/projects/63-basmah/node_modules/rollup/dist/es/shared/node-entry.js:21509:32
 ELIFECYCLE  Command failed with exit code 1.
Is that expected? wrapping dehydrate in a createServerFn fixes it
blank-aquamarine
blank-aquamarine4h ago
although dehydrate runs only on the server, it still must be isomorphic as getRouter must be isomorphic so you can either - supply different impls of getRouter via createIsomorphicFn - wrap the function you pass into dehydrate with createServerOnlyFn

Did you find this page helpful?