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.
| Locale | URL |
|---|---|
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.
| Locale | URL |
|---|---|
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:
| Locale | URL |
|---|---|
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.
Emit hreflang and canonical links
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:
Strategyis a plain string. There is nopiko.StrategyPrefixExceptDefaultconstant. 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.