Start hydration issue with localized head meta title using Lingui i18n
Which project does this relate to?
Start
Describe the bug
When using the Lingui i18n localization + TanStack Start template, when trying to use a localized meta title the page hydrates weirdly.
import { i18n } from "@lingui/core";
import { msg } from "@lingui/core/macro";
export const Route = createFileRoute("/")({
component: Home,
head: () => {
return {
meta: [
{
title: i18n._(msg`Test English title`),
},
],
};
},
});
Before hydration, the SSR render is all correct. The page title is localized and the content is correct.
After hydration, the contents appears twice, the top appears to be the SSR version (clicking links will load pages), the bottom appears to be the hydrated client (clicking links do not load pages). The localized title also does not work because there are two <title> in the HTML
<head>
<title>TanStack Start | Type-Safe, Client-First, Full-Stack React Framework</title>
<title>Test English title</title>
There are also no hydration errors in the console.
Your Example Website or App
https://codesandbox.io/p/devbox/nice-snowflake-yrx273?file=%2Fsrc%2Froutes%2Findex.tsx%3A3%2C1-4%2C42&workspaceId=ws_62aAsWkSzvrQC9UJ9wAyfv
Steps to Reproduce the Bug or Issue
- Go to the sandbox
- Open the preview in a new window (to see the page title)
- Iinitially the page title will be correct in the SSR version
- Wait a few seconds for the client hydration
- Page title will be incorrect and content will be doubled
Expected behavior
The page to be hydrated correctly, no double up. The page title to be localized.
Screenshots or Videos
SSR before hydration
After hydration
Platform
- OS: Windows
- Browser: Chrome
- Version: 137
Additional context
No response
does this still exist on the alpha branch?
does this still exist on the alpha branch?
I haven't been following the alpha branch, I can't quite get it to start, defineConfig got moved? Is there a guide somewhere with the breaking changes?
EDIT: Ah I saw the examples at https://github.com/TanStack/router/tree/alpha/examples/react/start-basic but I'm not seeing a way how to do custom client/server initialization
checkout https://github.com/TanStack/router/discussions/2863#discussioncomment-13104960
checkout #2863 (comment)
Sorry I'm a bit confused how defineEventHandler is supposed to be used now? It looks like createStartHandler no longer takes a event: H3Event<EventHandlerRequest>?
import { i18n } from "@lingui/core";
import {
createStartHandler,
defaultStreamHandler,
defineEventHandler,
} from "@tanstack/react-start/server";
import { createRouter } from "./router";
import { setupLocaleFromRequest } from "./modules/lingui/i18n.server";
export default defineEventHandler(async (event) => {
await setupLocaleFromRequest();
return createStartHandler({
createRouter: () => {
return createRouter({ i18n });
},
})(defaultStreamHandler)(event);
});
Also how do I customize the client.tsx now with custom logic?
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} />);
have a look at our e2e example on how server and client can be customized
https://github.com/TanStack/router/tree/alpha/e2e%2Freact-start%2Fbasic%2Fsrc
That link doesn't help at all, server.ts is just the default mentioned in the docs.
The docs here (I found it on X): https://tanstack-com.vercel.app/start/latest/docs/framework/react/server-routes#handling-server-route-requests mention a defineHandler function which doesn't exist and like @longzheng mentioned, createStartHandler no longer accepts an event from defineEventHandler.
I'm not 100% sure if the approach in Lingui's repo is the best solution to this problem, but basically I'd like to execute some logic on every route in the app, server side, based on the request headers. That might affect how router is rendered and might be async. How do I do that?
@paolostyle what exactly do you need to execute here? is this inside of react?
For now I'd like to just make the code posted in OP's last comment work:
import { i18n } from "@lingui/core";
import {
createStartHandler,
defaultStreamHandler,
defineEventHandler,
} from "@tanstack/react-start/server";
import { createRouter } from "./router";
import { setupLocaleFromRequest } from "./modules/lingui/i18n.server";
export default defineEventHandler(async (event) => {
await setupLocaleFromRequest();
return createStartHandler({
createRouter: () => {
return createRouter({ i18n });
},
})(defaultStreamHandler)(event);
});
setupLocaleFromRequest is defined here. But you might as well simplify it to await Promise.resolve() and remove passing the i18n to createRouter; it's just that I want to be able to await an async function BEFORE createStartHandler.
At the moment it does not work, because of TypeScript error Argument of type 'H3Event<EventHandlerRequest>' is not assignable to parameter of type '{ request: Request; }'. when trying to pass event at the very end; it also doesn't work in runtime.
For essentially the same reason e.g. @clerk/tanstack-react-start does not work in the devinxi/alpha version, at least on 1.121.0-alpha.21. TypeScript won't complain in your official example because you @ts-expect-errored it, but if you try to run pnpm dev on this example: https://github.com/TanStack/router/tree/v1.121.0-alpha.21/examples/react/start-clerk-basic you'll get An error occured while server rendering /index.html: No HTTPEvent found in AsyncLocalStorage. Make sure you are using the function within the server runtime..
Can confirm that the "No HTTPEvent found in AsyncLocalStorage" error is still present in the latest alpha (1.121.0-alpha.27).
@longzheng @paolostyle My working code with latest Tanstack Start 1.121.x:
// src/server.tsx
import { i18n } from '@lingui/core';
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server';
import { setupLocaleFromRequest } from './modules/lingui/i18n.server';
import { createRouter } from './router';
const defineHandler = ()=>(async (event) => {
await setupLocaleFromRequest();
const startHandler = createStartHandler({
createRouter: () => {
return createRouter({ i18n });
},
})(defaultStreamHandler);
return startHandler(event);
});
export default defineHandler();
// vite.config.ts
import { lingui } from '@lingui/vite-plugin';
import tailwindcss from '@tailwindcss/vite';
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
server: {
port: 3000,
},
plugins: [
lingui(),
tailwindcss(),
// Enables Vite to resolve imports using path aliases.
tsconfigPaths(),
tanstackStart({
react: {
babel: {
plugins: ['@lingui/babel-plugin-lingui-macro'],
},
},
}),
],
});
getting duplicate render on prod
I updated the lingui example here, let me know if you still have issues with the new template.
@depsimon it needs yet another update, the v1 RC version changed again API's, contracts, etc