Skip to content

Start hydration issue with localized head meta title using Lingui i18n #4279

Closed

Description

@longzheng

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

  1. Go to the sandbox
  2. Open the preview in a new window (to see the page title)
  3. Iinitially the page title will be correct in the SSR version
  4. Wait a few seconds for the client hydration
  5. 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

Image

After hydration

Image

Platform

  • OS: Windows
  • Browser: Chrome
  • Version: 137

Additional context

No response

Activity

  1. schiller-manuel commented on May 30, 2025

    @schiller-manuel
    Collaborator

    does this still exist on the alpha branch?

  2. longzheng commented on May 30, 2025

    @longzheng
    Author

    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

  3. schiller-manuel commented on May 30, 2025

    @schiller-manuel
    Collaborator
  4. longzheng commented on May 31, 2025

    @longzheng
    Author

    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} />);
    
  5. schiller-manuel commented on May 31, 2025

    @schiller-manuel
    Collaborator

    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

  6. paolostyle commented on May 31, 2025

    @paolostyle

    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?

  7. schiller-manuel commented on May 31, 2025

    @schiller-manuel
    Collaborator

    @paolostyle what exactly do you need to execute here? is this inside of react?

  8. paolostyle commented on May 31, 2025

    @paolostyle

    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..

  9. gunnartorfis commented on Jun 11, 2025

    @gunnartorfis

    Can confirm that the "No HTTPEvent found in AsyncLocalStorage" error is still present in the latest alpha (1.121.0-alpha.27).

  10. smoothdvd commented on Jun 12, 2025

    @smoothdvd

    @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'],
            },
          },
        }),
      ],
    });
    
    
  11. mtergel commented on Jun 15, 2025

    @mtergel

    getting duplicate render on prod

  12. depsimon commented on Jun 18, 2025

    @depsimon

    I updated the lingui example here, let me know if you still have issues with the new template.

  13. SutuSebastian commented on Oct 17, 2025

    @SutuSebastian

    @depsimon it needs yet another update, the v1 RC version changed again API's, contracts, etc

  14. LadyBluenotes commented on Jul 13, 2026

    @LadyBluenotes
    Member

    Closing this as fixed. If the latest Lingui example still produces duplicated content or head tags because of Start, please open a new issue with the current versions and reproduction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions