{"id":1512,"date":"2026-06-19T13:00:00","date_gmt":"2026-06-19T18:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1512"},"modified":"2026-03-07T03:49:30","modified_gmt":"2026-03-07T08:49:30","slug":"hreflang-app-deep-links","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/hreflang-app-deep-links\/","title":{"rendered":"Hreflang for App Deep Links: International SEO"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">If your app serves multiple languages or regions, search engines need to know which version of each page to show to which audience. That is what <a href=\"https:\/\/developers.google.com\/search\/docs\/specialty\/international\/localized-versions\" rel=\"nofollow noopener\" target=\"_blank\">hreflang<\/a> does. Without it, Google may show the English version of your product page to a French user, or the US-priced listing to someone in Germany.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For apps with deep links, hreflang adds another layer: you need to coordinate the web page language variants with the app deep link targets. This guide covers how to do that correctly. For the general app indexing strategy, see <a href=\"https:\/\/tolinku.com\/blog\/app-indexing-seo-mobile-apps\/\">app indexing and SEO for mobile apps<\/a>. For canonical URL strategy across app content, see <a href=\"https:\/\/tolinku.com\/blog\/canonical-urls-app-content\/\">canonical URLs for app content<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Hreflang Works<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">The Basics<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Hreflang tells search engines: &quot;This page exists in multiple language\/region versions. Here are the URLs for each version.&quot; Google then serves the correct version based on the searcher&#39;s language and location.<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;head&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;en&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;es&quot; href=&quot;https:\/\/www.yourapp.com\/es\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;fr&quot; href=&quot;https:\/\/www.yourapp.com\/fr\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;de&quot; href=&quot;https:\/\/www.yourapp.com\/de\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;x-default&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n&lt;\/head&gt;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>x-default<\/code> value specifies the fallback version for users whose language\/region does not match any listed variant.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Language vs. Region<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Hreflang supports both language-only and language-region codes:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Code<\/th>\n<th>Meaning<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td><code>en<\/code><\/td>\n<td>English (any region)<\/td>\n<\/tr>\n<tr>\n<td><code>en-US<\/code><\/td>\n<td>English for United States<\/td>\n<\/tr>\n<tr>\n<td><code>en-GB<\/code><\/td>\n<td>English for United Kingdom<\/td>\n<\/tr>\n<tr>\n<td><code>es<\/code><\/td>\n<td>Spanish (any region)<\/td>\n<\/tr>\n<tr>\n<td><code>es-MX<\/code><\/td>\n<td>Spanish for Mexico<\/td>\n<\/tr>\n<tr>\n<td><code>pt-BR<\/code><\/td>\n<td>Portuguese for Brazil<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Use language-region codes when the content differs by region (pricing, availability, legal terms). Use language-only codes when all regions share the same translation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">URL Structures for Multilingual Deep Links<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Option 1: Subdirectory per Language<\/h3>\n\n\n\n<pre><code>https:\/\/www.yourapp.com\/products\/shoes          (English, default)\nhttps:\/\/www.yourapp.com\/es\/products\/shoes       (Spanish)\nhttps:\/\/www.yourapp.com\/fr\/products\/shoes       (French)\nhttps:\/\/www.yourapp.com\/de\/products\/shoes       (German)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is the simplest approach. All content lives on one domain, preserving link equity. The deep link path includes the language prefix.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>App deep link handling:<\/strong><\/p>\n\n\n\n<pre><code class=\"language-swift\">\/\/ iOS: Extract language from URL path\nfunc handleDeepLink(_ url: URL) {\n    let pathComponents = url.pathComponents\n    let supportedLanguages = [&quot;es&quot;, &quot;fr&quot;, &quot;de&quot;, &quot;ja&quot;, &quot;ko&quot;]\n\n    var language = &quot;en&quot;\n    var contentPath = url.path\n\n    if pathComponents.count &gt; 1, supportedLanguages.contains(pathComponents[1]) {\n        language = pathComponents[1]\n        contentPath = &quot;\/&quot; + pathComponents.dropFirst(2).joined(separator: &quot;\/&quot;)\n    }\n\n    setAppLanguage(language)\n    navigateToContent(contentPath)\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Option 2: Separate Domains per Region<\/h3>\n\n\n\n<pre><code>https:\/\/www.yourapp.com\/products\/shoes          (US)\nhttps:\/\/www.yourapp.co.uk\/products\/shoes        (UK)\nhttps:\/\/www.yourapp.de\/products\/shoes           (Germany)\nhttps:\/\/www.yourapp.com.br\/products\/shoes       (Brazil)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Each domain needs its own <a href=\"https:\/\/developer.android.com\/training\/app-links\" rel=\"nofollow noopener\" target=\"_blank\">App Links<\/a> and <a href=\"https:\/\/developer.apple.com\/documentation\/xcode\/allowing-apps-and-websites-to-link-to-your-content\" rel=\"nofollow noopener\" target=\"_blank\">Universal Links<\/a> verification files. This multiplies the configuration work.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Option 3: Subdomain per Language<\/h3>\n\n\n\n<pre><code>https:\/\/en.yourapp.com\/products\/shoes\nhttps:\/\/es.yourapp.com\/products\/shoes\nhttps:\/\/fr.yourapp.com\/products\/shoes\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Similar to separate domains in terms of verification complexity. Each subdomain needs its own <code>assetlinks.json<\/code> and <code>apple-app-site-association<\/code> files.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Recommendation:<\/strong> Use subdirectories unless you have strong business reasons for separate domains (different legal entities, separate app builds per region).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hreflang Implementation for Deep Link Pages<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">HTML Link Tags<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Add hreflang tags to every language variant of each page:<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;!-- On https:\/\/www.yourapp.com\/products\/shoes --&gt;\n&lt;head&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;en&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;es&quot; href=&quot;https:\/\/www.yourapp.com\/es\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;fr&quot; href=&quot;https:\/\/www.yourapp.com\/fr\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;x-default&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;canonical&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n&lt;\/head&gt;\n\n&lt;!-- On https:\/\/www.yourapp.com\/es\/products\/shoes --&gt;\n&lt;head&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;en&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;es&quot; href=&quot;https:\/\/www.yourapp.com\/es\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;fr&quot; href=&quot;https:\/\/www.yourapp.com\/fr\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;alternate&quot; hreflang=&quot;x-default&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n  &lt;link rel=&quot;canonical&quot; href=&quot;https:\/\/www.yourapp.com\/es\/products\/shoes&quot; \/&gt;\n&lt;\/head&gt;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Every page must reference all other language variants, including itself. This bidirectional confirmation is how Google validates the hreflang setup.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Sitemap Implementation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For large apps with thousands of pages, implementing hreflang in sitemaps is more maintainable than HTML tags:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;\n&lt;urlset xmlns=&quot;http:\/\/www.sitemaps.org\/schemas\/sitemap\/0.9&quot;\n        xmlns:xhtml=&quot;http:\/\/www.w3.org\/1999\/xhtml&quot;&gt;\n  &lt;url&gt;\n    &lt;loc&gt;https:\/\/www.yourapp.com\/products\/shoes&lt;\/loc&gt;\n    &lt;xhtml:link rel=&quot;alternate&quot; hreflang=&quot;en&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n    &lt;xhtml:link rel=&quot;alternate&quot; hreflang=&quot;es&quot; href=&quot;https:\/\/www.yourapp.com\/es\/products\/shoes&quot; \/&gt;\n    &lt;xhtml:link rel=&quot;alternate&quot; hreflang=&quot;fr&quot; href=&quot;https:\/\/www.yourapp.com\/fr\/products\/shoes&quot; \/&gt;\n    &lt;xhtml:link rel=&quot;alternate&quot; hreflang=&quot;x-default&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n  &lt;\/url&gt;\n  &lt;url&gt;\n    &lt;loc&gt;https:\/\/www.yourapp.com\/es\/products\/shoes&lt;\/loc&gt;\n    &lt;xhtml:link rel=&quot;alternate&quot; hreflang=&quot;en&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n    &lt;xhtml:link rel=&quot;alternate&quot; hreflang=&quot;es&quot; href=&quot;https:\/\/www.yourapp.com\/es\/products\/shoes&quot; \/&gt;\n    &lt;xhtml:link rel=&quot;alternate&quot; hreflang=&quot;fr&quot; href=&quot;https:\/\/www.yourapp.com\/fr\/products\/shoes&quot; \/&gt;\n    &lt;xhtml:link rel=&quot;alternate&quot; hreflang=&quot;x-default&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n  &lt;\/url&gt;\n&lt;\/urlset&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Deep Link Routing by Language<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Server-Side Language Detection<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a deep link URL is accessed, detect the user&#39;s language and route accordingly:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">app.get(&#39;\/products\/:slug&#39;, (req, res) =&gt; {\n  const acceptLanguage = req.headers[&#39;accept-language&#39;];\n  const userLang = parseLanguage(acceptLanguage); \/\/ e.g., &#39;es&#39;\n  const localizedPath = userLang !== &#39;en&#39; ? `\/${userLang}\/products\/${req.params.slug}` : req.path;\n\n  \/\/ For app users: deep link opens the localized content\n  \/\/ For web users: redirect to the localized page\n  if (isBot(req.headers[&#39;user-agent&#39;])) {\n    \/\/ Serve the English version to bots (they follow hreflang for other versions)\n    renderPage(req.params.slug, &#39;en&#39;, res);\n  } else {\n    res.redirect(302, localizedPath);\n  }\n});\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">App-Side Language Handling<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When the app opens a deep link, use the URL&#39;s language prefix to set the content language:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">\/\/ Android: handle localized deep links\nclass ProductActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        val uri = intent.data ?: return\n        val pathSegments = uri.pathSegments\n\n        \/\/ Check if first segment is a language code\n        val supportedLangs = setOf(&quot;es&quot;, &quot;fr&quot;, &quot;de&quot;, &quot;ja&quot;, &quot;ko&quot;, &quot;pt&quot;)\n        val lang = if (pathSegments.isNotEmpty() &amp;&amp; supportedLangs.contains(pathSegments[0])) {\n            pathSegments[0]\n        } else {\n            &quot;en&quot;\n        }\n\n        val slug = pathSegments.last()\n        viewModel.loadProduct(slug, lang)\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Structured Data for Multilingual Content<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Localized Structured Data<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Each language variant should have its own structured data with localized values:<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;!-- English version --&gt;\n&lt;script type=&quot;application\/ld+json&quot;&gt;\n{\n  &quot;@context&quot;: &quot;https:\/\/schema.org&quot;,\n  &quot;@type&quot;: &quot;Product&quot;,\n  &quot;name&quot;: &quot;Running Shoes&quot;,\n  &quot;description&quot;: &quot;Lightweight running shoes for daily training&quot;,\n  &quot;offers&quot;: {\n    &quot;@type&quot;: &quot;Offer&quot;,\n    &quot;price&quot;: &quot;120.00&quot;,\n    &quot;priceCurrency&quot;: &quot;USD&quot;\n  },\n  &quot;potentialAction&quot;: {\n    &quot;@type&quot;: &quot;ViewAction&quot;,\n    &quot;target&quot;: &quot;https:\/\/www.yourapp.com\/products\/running-shoes&quot;\n  }\n}\n&lt;\/script&gt;\n\n&lt;!-- Spanish version --&gt;\n&lt;script type=&quot;application\/ld+json&quot;&gt;\n{\n  &quot;@context&quot;: &quot;https:\/\/schema.org&quot;,\n  &quot;@type&quot;: &quot;Product&quot;,\n  &quot;name&quot;: &quot;Zapatillas de Running&quot;,\n  &quot;description&quot;: &quot;Zapatillas ligeras para entrenamiento diario&quot;,\n  &quot;offers&quot;: {\n    &quot;@type&quot;: &quot;Offer&quot;,\n    &quot;price&quot;: &quot;110.00&quot;,\n    &quot;priceCurrency&quot;: &quot;EUR&quot;\n  },\n  &quot;potentialAction&quot;: {\n    &quot;@type&quot;: &quot;ViewAction&quot;,\n    &quot;target&quot;: &quot;https:\/\/www.yourapp.com\/es\/products\/running-shoes&quot;\n  }\n}\n&lt;\/script&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Common Mistakes<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Missing Return Links<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Every hreflang annotation must be bidirectional. If page A says &quot;my Spanish version is page B,&quot; page B must say &quot;my English version is page A.&quot; If either side is missing, Google ignores the entire hreflang relationship for that pair.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Conflicting Canonical and Hreflang<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Do not set the canonical URL of a localized page to the default language version. Each language variant should have its own self-referencing canonical:<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;!-- Wrong: Spanish page canonicalized to English --&gt;\n&lt;link rel=&quot;canonical&quot; href=&quot;https:\/\/www.yourapp.com\/products\/shoes&quot; \/&gt;\n\n&lt;!-- Correct: Spanish page canonicalized to itself --&gt;\n&lt;link rel=&quot;canonical&quot; href=&quot;https:\/\/www.yourapp.com\/es\/products\/shoes&quot; \/&gt;\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Missing x-default<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Always include <code>x-default<\/code> to handle users whose language\/region does not match any variant. Without it, Google guesses which version to show.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku and International Deep Links<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a> supports deep link routing with path-based rules. Configure routes like <code>\/:lang\/products\/:slug<\/code> in the <a href=\"https:\/\/tolinku.com\/docs\/concepts\/deep-linking\/\">Tolinku dashboard<\/a> to handle localized deep links. The route parameters pass the language code to the app, which loads the content in the correct language.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For banner localization on multilingual sites, see <a href=\"https:\/\/tolinku.com\/blog\/banner-localization\/\">localizing smart banners<\/a>. For the broader app indexing strategy, see <a href=\"https:\/\/tolinku.com\/blog\/app-indexing-seo-mobile-apps\/\">app indexing and SEO for mobile apps<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Handle international SEO for app deep links. Use hreflang tags to serve the right app content for different languages and regions.<\/p>\n","protected":false},"author":2,"featured_media":1511,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Hreflang for App Deep Links: International SEO","rank_math_description":"Handle international SEO for app deep links. Use hreflang tags to serve the right app content for different languages and regions.","rank_math_focus_keyword":"hreflang app deep links","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-hreflang-app-deep-links.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-hreflang-app-deep-links.png","footnotes":""},"categories":[16],"tags":[20,382,383,248,69,384,358,63],"class_list":["post-1512","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-marketing","tag-deep-linking","tag-hreflang","tag-international-seo","tag-localization","tag-mobile-development","tag-multilingual","tag-search","tag-seo"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1512","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1512"}],"version-history":[{"count":4,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1512\/revisions"}],"predecessor-version":[{"id":2620,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1512\/revisions\/2620"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1511"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1512"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1512"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1512"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}