Skip to content
FrameworkStyle

SSR and locale

Render translated player UI on the server without a flash of English

Video.js is English by default. Server-rendered pages that opt into another language should mount the relevant provider, output the correct lang, and supply translations on the first client render. Lazy loadLocale and browser translation fallback run after hydration, so controls briefly show English without preloaded copy.

Read Internationalization for provider resolution and merge order.

Set <html lang>

Render the document language on the server:

<html lang="es">

React providers use a server snapshot of undefined for ambient lang, then read <html lang> on hydration. Passing the locale prop explicitly avoids any mismatch.

Preload translations

Import the locale pack on the server (or in a server component) and pass it to I18nProvider:

import { I18nProvider } from '@videojs/react/i18n';
import { Provider, VideoSkin, Video } from '@videojs/react/video';

export async function Player({ locale }: { locale: string }) {
  const { default: translations } = await import(`@videojs/react/i18n/locales/${locale}`);

  return (
    <Provider>
      <I18nProvider locale={locale} translations={translations}>
        <VideoSkin>
          <Video src="..." playsInline />
        </VideoSkin>
      </I18nProvider>
    </Provider>
  );
}

translations on the first render skips the async lazy layer and Chrome-only browser translation fallback. Labels match on server and client.

Register on the server

For a shipped pack, import its side-effect module in server bootstrap code before rendering players:

import '@videojs/react/i18n/locales/es/register';

For a custom pack, call registerI18n instead:

import { registerI18n } from '@videojs/react/i18n';
import es from './locales/es';

registerI18n('es', es);

The registry is module singleton state. Registration in server entry points carries into the client bundle when shared.

next-intl and app routers

import { getLocale } from 'next-intl/server';

const locale = await getLocale();
const { default: translations } = await import(`@videojs/react/i18n/locales/${locale}`);

<I18nProvider locale={locale} translations={translations}>
  ...
</I18nProvider>

Match your app’s locale negotiation. Video.js does not replace framework i18n; it consumes the tag you pass.

What’s next?