Packages

phoenix_kit

2.13.1
2.13.1 2.13.0 2.12.1 2.12.0 2.11.0 2.10.0 2.9.0 2.8.1 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.0 2.0.1 2.0.0 1.7.236 1.7.235 1.7.234 1.7.233 1.7.232 1.7.231 1.7.230 1.7.229 1.7.228 1.7.227 1.7.226 1.7.225 1.7.224 1.7.223 1.7.222 1.7.221 1.7.220 1.7.219 1.7.218 1.7.217 1.7.216 1.7.215 1.7.214 1.7.213 1.7.212 1.7.211 1.7.210 1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit_web users auth_seo.ex
Raw

lib/phoenix_kit_web/users/auth_seo.ex

defmodule PhoenixKitWeb.Users.AuthSEO do
@moduledoc """
Canonical / hreflang assigns for the auth LiveViews whose path is stable
and token-free (registration, log-in, magic-link registration request).
Mirrors `PhoenixKitEcommerce.Web.SEOHelpers`'s contract so a multi-domain
host wires one resolver for both the shop and auth surfaces: the canonical
host per language comes from the workspace-wide
`config :phoenix_kit, :canonical_host_resolver, {mod, fun}` MFA (nil or
absent ⇒ the site-wide `PhoenixKit.Utils.Routes.base_url/0`, today's
behavior). On a language's own resolved host the locale segment is dropped
(`Routes.path(url_path, locale: :none)`) — the domain itself carries the
language, so a `/en` (or any other) prefix on top of it would be redundant.
Deliberately excludes every token-bearing auth page (email confirmation,
password reset, magic-link verify, QR-login confirm): a one-time token in
the URL makes canonical/hreflang meaningless — there is no duplicate-content
signal to consolidate — and actively wrong, since the link a search engine
would be told about goes dead the moment the token is spent.
Setting these assigns is enough on its own: the root layout that renders
`@canonical_url` / `@hreflang_links` into `<head>` is the HOST app's, per
the same cross-package contract `phoenix_kit_ecommerce`'s catalog pages
already rely on (see `PhoenixKitEcommerce.Web.SEOHelpers`). Nothing here
renders anything itself.
"""
require Logger
alias PhoenixKit.Modules.Languages
alias PhoenixKit.Modules.Languages.DialectMapper
alias PhoenixKit.Utils.Routes
@doc """
Canonical + hreflang assigns for a stable, token-free auth path, e.g.
`"/users/register"`.
`hreflang_links` is `[]` when Languages is disabled or fewer than two
languages are enabled — one language is not a hreflang set.
"""
@spec seo_assigns(String.t()) :: %{canonical_url: String.t(), hreflang_links: [map()]}
def seo_assigns(url_path) when is_binary(url_path) do
%{
canonical_url: canonical_absolute_url(current_language(), url_path),
hreflang_links: hreflang_links(url_path)
}
end
defp hreflang_links(url_path) do
links =
enabled_languages()
|> Enum.map(&DialectMapper.extract_base(&1.code))
|> Enum.uniq()
|> Enum.map(&%{code: &1, url: canonical_absolute_url(&1, url_path)})
if length(links) < 2, do: [], else: links
end
defp enabled_languages do
if Languages.enabled?(), do: Languages.get_enabled_languages(), else: []
end
# These assigns are computed from inside `mount/3`, before the locale
# `handle_params` hook (`PhoenixKitWeb.Users.Auth.attach_locale_hook/1`)
# has run — so `socket.assigns[:current_locale_base]` is still unset here.
# The HTTP-layer locale plug already put the right value into Gettext
# before the LiveView mounted, which is exactly what `Routes.path/2` (called
# elsewhere in the same `do_mount`, e.g. for presence tracking) relies on
# too when it's not given an explicit `:locale`.
defp current_language do
PhoenixKitWeb.Gettext
|> Gettext.get_locale()
|> DialectMapper.extract_base()
end
defp canonical_absolute_url(language, url_path) do
case resolve_canonical_host(language) do
nil ->
Routes.base_url() <> Routes.path(url_path, locale: language)
host ->
# The language's own home domain implies the language — no locale
# segment needed on top of it, for the primary language or any other.
"https://#{host}" <> Routes.path(url_path, locale: :none)
end
end
defp resolve_canonical_host(language) do
case Application.get_env(:phoenix_kit, :canonical_host_resolver) do
{mod, fun} -> apply(mod, fun, [language])
_ -> nil
end
rescue
error ->
Logger.warning(
"[AuthSEO] canonical_host_resolver raised, falling back to site base: " <>
Exception.message(error)
)
nil
catch
# A connection-pool failure under the host's resolver EXITS rather than
# raising (see `Routes.get_default_language_base/0` for the same class of
# bug) — `rescue` alone does not catch it.
:exit, reason ->
Logger.warning(
"[AuthSEO] canonical_host_resolver exited, falling back to site base: " <>
inspect(reason)
)
nil
end
end