add SEO enhancements: OG images, meta robots, FAQ block, image sitemap
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:
jamey
2026-04-17 16:47:43 +01:00
parent 9facfd926e
commit 4aa7dece0c
42 changed files with 3881 additions and 41 deletions

View File

@@ -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