add SEO enhancements: OG images, meta robots, FAQ block, image sitemap
All checks were successful
deploy / deploy (push) Successful in 4m59s
All checks were successful
deploy / deploy (push) Successful in 4m59s
- Per-page SEO controls: meta robots directives, focus keyword, OG image - Site-wide default OG image in admin settings - FAQ block type with FAQPage JSON-LD schema - Enhanced Organization JSON-LD with business info, contact, address - Image sitemap with product images - SEO preview panel with Google/social card mockups - SEO checklist with real-time scoring - Business info section in site editor - GSC integration scaffolding (OAuth, client, cache) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,8 @@ defmodule BerrypodWeb.Shop.Pages.Home do
|
||||
|
||||
import Phoenix.Component, only: [assign: 2, assign: 3]
|
||||
|
||||
alias Berrypod.Pages
|
||||
alias Berrypod.{Pages, Settings}
|
||||
alias BerrypodWeb.Helpers.SeoHelpers
|
||||
|
||||
def init(socket, _params, _uri) do
|
||||
page = Pages.get_page("home")
|
||||
@@ -14,28 +15,145 @@ defmodule BerrypodWeb.Shop.Pages.Home do
|
||||
base = BerrypodWeb.Endpoint.url()
|
||||
site_name = socket.assigns.site_name
|
||||
|
||||
org_ld =
|
||||
Jason.encode!(
|
||||
%{
|
||||
"@context" => "https://schema.org",
|
||||
"@type" => "Organization",
|
||||
"name" => site_name,
|
||||
"url" => base <> "/"
|
||||
},
|
||||
escape: :html_safe
|
||||
)
|
||||
org_ld = build_organization_json_ld(socket.assigns, base, site_name)
|
||||
json_ld = combine_json_ld([org_ld, SeoHelpers.faq_json_ld(page.blocks)])
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:page_title, "Home")
|
||||
|> assign(:og_url, base <> "/")
|
||||
|> assign(:json_ld, org_ld)
|
||||
|> assign(:json_ld, json_ld)
|
||||
|> assign(:page, page)
|
||||
|> maybe_assign_meta_robots(page)
|
||||
|> SeoHelpers.assign_og_image(page, base)
|
||||
|> assign(extra)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
# Combine multiple JSON-LD scripts into a single output (newline-separated)
|
||||
defp combine_json_ld(ld_list) do
|
||||
ld_list
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> case do
|
||||
[] -> nil
|
||||
[single] -> single
|
||||
many -> Enum.join(many, "\n</script>\n<script type=\"application/ld+json\">\n")
|
||||
end
|
||||
end
|
||||
|
||||
defp build_organization_json_ld(assigns, base_url, site_name) do
|
||||
business_info = Settings.get_business_info()
|
||||
|
||||
org_type =
|
||||
if business_info["business_type"] == "LocalBusiness",
|
||||
do: "LocalBusiness",
|
||||
else: "Organization"
|
||||
|
||||
org = %{
|
||||
"@context" => "https://schema.org",
|
||||
"@type" => org_type,
|
||||
"name" => site_name,
|
||||
"url" => base_url <> "/"
|
||||
}
|
||||
|
||||
org
|
||||
|> maybe_add_logo(assigns[:logo_image], base_url)
|
||||
|> maybe_add_contact_point(business_info)
|
||||
|> maybe_add_address(business_info)
|
||||
|> maybe_add_same_as(assigns[:social_links])
|
||||
|> Jason.encode!(escape: :html_safe)
|
||||
end
|
||||
|
||||
defp maybe_add_logo(org, nil, _base_url), do: org
|
||||
|
||||
defp maybe_add_logo(org, logo_image, base_url) do
|
||||
logo_url = base_url <> "/image_cache/#{logo_image.id}.webp"
|
||||
Map.put(org, "logo", logo_url)
|
||||
end
|
||||
|
||||
defp maybe_add_contact_point(org, business_info) do
|
||||
phone = business_info["business_phone"]
|
||||
email = business_info["business_email"]
|
||||
|
||||
cond do
|
||||
present?(phone) and present?(email) ->
|
||||
Map.put(org, "contactPoint", [
|
||||
%{"@type" => "ContactPoint", "telephone" => phone, "contactType" => "customer service"},
|
||||
%{"@type" => "ContactPoint", "email" => email, "contactType" => "customer service"}
|
||||
])
|
||||
|
||||
present?(phone) ->
|
||||
Map.put(org, "contactPoint", %{
|
||||
"@type" => "ContactPoint",
|
||||
"telephone" => phone,
|
||||
"contactType" => "customer service"
|
||||
})
|
||||
|
||||
present?(email) ->
|
||||
Map.put(org, "contactPoint", %{
|
||||
"@type" => "ContactPoint",
|
||||
"email" => email,
|
||||
"contactType" => "customer service"
|
||||
})
|
||||
|
||||
true ->
|
||||
org
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_add_address(org, business_info) do
|
||||
street = business_info["address_street"]
|
||||
city = business_info["address_city"]
|
||||
country = business_info["address_country"]
|
||||
|
||||
if present?(street) or present?(city) or present?(country) do
|
||||
address = %{"@type" => "PostalAddress"}
|
||||
|
||||
address =
|
||||
address
|
||||
|> maybe_put("streetAddress", street)
|
||||
|> maybe_put("addressLocality", city)
|
||||
|> maybe_put("addressRegion", business_info["address_region"])
|
||||
|> maybe_put("postalCode", business_info["address_postal_code"])
|
||||
|> maybe_put("addressCountry", country)
|
||||
|
||||
Map.put(org, "address", address)
|
||||
else
|
||||
org
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_add_same_as(org, nil), do: org
|
||||
defp maybe_add_same_as(org, []), do: org
|
||||
|
||||
defp maybe_add_same_as(org, social_links) do
|
||||
urls =
|
||||
social_links
|
||||
|> Enum.map(& &1.url)
|
||||
|> Enum.filter(&present?/1)
|
||||
|
||||
if urls != [], do: Map.put(org, "sameAs", urls), else: org
|
||||
end
|
||||
|
||||
defp maybe_put(map, _key, nil), do: map
|
||||
defp maybe_put(map, _key, ""), do: map
|
||||
defp maybe_put(map, key, value), do: Map.put(map, key, value)
|
||||
|
||||
defp present?(nil), do: false
|
||||
defp present?(""), do: false
|
||||
defp present?(_), do: true
|
||||
|
||||
defp maybe_assign_meta_robots(socket, page) do
|
||||
meta_robots = page && page[:meta_robots]
|
||||
|
||||
if meta_robots && meta_robots != "index, follow" do
|
||||
assign(socket, :meta_robots, meta_robots)
|
||||
else
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
||||
def handle_params(_params, _uri, socket) do
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user