There are quite a few SEO related things you need to keep in mind when developing a multi-language site. This page will give you a quick overview of the most important ones.

Alternate links are a way to tell search engines that a page exists in multiple languages, and where to find them. This is done by adding a link tag to the head of your page.

You should add a link tag for each language your site is available in, including the one the page is currently in.

    <link rel="alternate" href="/en" hreflang="en" />
    <link rel="alternate" href="/de" hreflang="de" />

If you have a “default language” that you want to use when the user’s language is not available, you should add a link tag with the hreflang attribute set to x-default. This tells search engines that this is the default language.

    <link rel="alternate" href="/en" hreflang="en" />
    <link rel="alternate" href="/de" hreflang="de" />

    <!--Use the english site as the default-->
    <link rel="alternate" href="/en"; hreflang="x-default" />

Locale Switchers

It is recommended that you use a tags for your locale switchers. This is because search engines and the SvelteKit prerenderer will follow these links, and index the pages they lead to. They also work if JavaScript is disabled.

But, we need to make sure to tell the search engines that these links just lead to the same page in a different language, not separate pages. We do this by adding an hreflang attribute.

<a href="/de" hreflang="de">Deutsch</a>

The Lang Attribute

Browsers determine the page’s language by looking at the lang attribute on the html tag. We need to make sure that this attribute is set to the correct language, both during server rendering, and when switching languages on the client.

On the Server

SvelteKit offers a relatively simple way to set the lang attribute during server rendering. We can set it in a hook.

In the app template, let’s add a placeholder string in the lang attribute.


<!DOCTYPE html>
<html lang="%lang%">
	<!-- ... -->

Then in the server handle hook, we can replace it with the correct language.


export const handle = async ({ event, resolve }) => {
  //Determine the locale from the URL.
  //Implementing this is up to you, depending on your routing solution.
  const locale = getLocale(event);

  const response = await resolve(event, {
    //Replace the placeholder %lang% with the current locale.
    transformPageChunk({ html }) {
      html = html.replace("%lang%", locale);
      return html;

  return response;

On the client

T18S does not do a full page reload when switching languages, so we need to make sure that the lang attribute gets set correctly when switching languages on the client.

In the root layout. Check that we are in the browser, and then reactively set the lang attribute base on the $locale store exported by T18S.


    import { locale } from "$t18s";
    import { browser } from "$app/environment";
    $: if(browser) document.documentElement.lang = $locale;
<slot /> 

This may become built in behavior in the future, depending on feedback. Old code probably won't break, so you can add this now without worrying about it.

Text Direction

It’s important to set the dir attribute on the html tag to the correct value. This can be done in the same way as the lang attribute.

You can get the current locale’s direction using the built in Intl API.


const dir = new Intl.Locale(get(locale)).textInfo.direction;

You then do the same thing as with the lang attribute.