How to choose an i18n routing strategy

Piko offers three URL strategies for multi-locale sites. The choice affects SEO, bookmarkability, and the complexity of the reverse-proxy configuration. This guide compares them. See the i18n API reference for the surrounding API.

query-only (default)

A single path per page. Piko detects the locale from a query parameter, the request, or the default. The URL stays bare.

LocaleURL
en (default)/about
fr/about?locale=fr (or detected from Accept-Language, cookie, etc.)
de/about?locale=de

The canonical query parameter is locale (read in internal/templater/templater_domain/parse_request.go).

Configure it in func main (this is also the default when you omit Strategy):

ssr := piko.New(
    piko.WithWebsiteConfig(piko.WebsiteConfig{
        I18n: piko.I18nConfig{
            DefaultLocale: "en",
            Strategy:      "query-only",
            Locales:       []string{"en", "fr", "de"},
        },
    }),
)

Use this strategy when SEO impact is minor or when a CDN handles per-locale variants below the URL layer. It keeps the URL space simple.

prefix_except_default

The default locale uses bare URLs. Every other locale prefixes its code.

LocaleURL
en (default)/about
fr/fr/about
de/de/about
piko.WithWebsiteConfig(piko.WebsiteConfig{
    I18n: piko.I18nConfig{
        DefaultLocale: "en",
        Strategy:      "prefix_except_default",
        Locales:       []string{"en", "fr", "de"},
    },
})

Use this strategy for sites with a clear primary language where the default is also the canonical SEO target.

prefix

Every locale, including the default, has a prefix:

LocaleURL
en/en/about
fr/fr/about
de/de/about
piko.WithWebsiteConfig(piko.WebsiteConfig{
    I18n: piko.I18nConfig{
        DefaultLocale: "en",
        Strategy:      "prefix",
        Locales:       []string{"en", "fr", "de"},
    },
})

Use this strategy for sites with comparable locale weight where no single language is the canonical default.

Note: Piko routes pages per locale only when the page declares SupportedLocales() in its Go script block. A page that omits that function emits a single route under the default locale regardless of the configured strategy. See how to enable i18n routing for a page.

Piko emits the canonical URL and hreflang alternate-link metadata automatically. When a page declares SupportedLocales(), the framework derives Metadata.Language, Metadata.CanonicalURL, and Metadata.AlternateLinks from the page's registered per-locale route patterns, so it needs no per-page SEO code.

import "piko.sh/piko"

func SupportedLocales() []string { return []string{"en", "fr", "de"} }

func Render(r *piko.RequestData, props Props) (Response, piko.Metadata, error) {
    // CanonicalURL and AlternateLinks are derived from the page's locale routes.
    return Response{}, piko.Metadata{Title: props.PageTitle}, nil
}

The canonical origin comes from the configured site URL (the SEO sitemap hostname) when set, otherwise the request host. A page may still set Metadata.CanonicalURL or Metadata.AlternateLinks explicitly to override the derived values.

Note: Strategy is a plain string. There is no piko.StrategyPrefixExceptDefault constant. Use the literal "prefix_except_default", "prefix", or "query-only".

Search engines use the alternate links to understand which pages are translations of which.

Detect the active locale

From any Render or action:

locale := r.Locale()              // e.g. "fr"
defaultLocale := r.DefaultLocale() // e.g. "en"

Serve locale-aware pages from a partial

The compiler resolves the is attribute on <piko:partial> at compile time, and it must be a static literal. The annotator rejects a dynamic expression like :is="'hero-' + state.Locale". Branch with p-if to pick a literal partial per locale instead, then put the locale-specific content inside each:

<template>
  <piko:partial p-if="state.Locale == 'fr'" is="hero-fr" />
  <piko:partial p-else-if="state.Locale == 'es'" is="hero-es" />
  <piko:partial p-else is="hero-en" />
</template>

<script type="application/x-go">
package main

import "piko.sh/piko"

type Response struct {
    Locale string
}

func Render(r *piko.RequestData, props piko.NoProps) (Response, piko.Metadata, error) {
    return Response{Locale: r.Locale()}, piko.Metadata{}, nil
}
</script>

With partials named hero-en, hero-fr, and hero-de, Piko selects the correct one per request.

See also