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:
@@ -26,48 +26,66 @@ defmodule BerrypodWeb.SeoController do
|
||||
|
||||
def sitemap(conn, _params) do
|
||||
base = BerrypodWeb.Endpoint.url()
|
||||
products = Products.list_visible_products()
|
||||
products = Products.list_products_for_sitemap()
|
||||
categories = Products.list_categories()
|
||||
|
||||
static_pages = [
|
||||
{R.home(), "daily", "1.0"},
|
||||
{R.collection("all"), "daily", "0.9"},
|
||||
{R.about(), "monthly", "0.5"},
|
||||
{R.contact(), "monthly", "0.5"},
|
||||
{R.delivery(), "monthly", "0.5"},
|
||||
{R.privacy(), "monthly", "0.3"},
|
||||
{R.terms(), "monthly", "0.3"}
|
||||
{R.home(), "daily", "1.0", []},
|
||||
{R.collection("all"), "daily", "0.9", []},
|
||||
{R.about(), "monthly", "0.5", []},
|
||||
{R.contact(), "monthly", "0.5", []},
|
||||
{R.delivery(), "monthly", "0.5", []},
|
||||
{R.privacy(), "monthly", "0.3", []},
|
||||
{R.terms(), "monthly", "0.3", []}
|
||||
]
|
||||
|
||||
category_pages =
|
||||
Enum.map(categories, fn cat ->
|
||||
{R.collection(cat.slug), "daily", "0.8"}
|
||||
{R.collection(cat.slug), "daily", "0.8", []}
|
||||
end)
|
||||
|
||||
product_pages =
|
||||
Enum.map(products, fn product ->
|
||||
{R.product(product.slug), "weekly", "0.9"}
|
||||
images = product_image_entries(product, base)
|
||||
{R.product(product.slug), "weekly", "0.9", images}
|
||||
end)
|
||||
|
||||
custom_pages =
|
||||
Pages.list_custom_pages()
|
||||
|> Enum.filter(& &1.published)
|
||||
|> Enum.map(fn page -> {"/#{page.slug}", "weekly", "0.6"} end)
|
||||
|> Enum.map(fn page -> {"/#{page.slug}", "weekly", "0.6", []} end)
|
||||
|
||||
all_pages = static_pages ++ category_pages ++ product_pages ++ custom_pages
|
||||
|
||||
entries =
|
||||
Enum.map_join(all_pages, "\n", fn {path, changefreq, priority} ->
|
||||
" <url>\n" <>
|
||||
" <loc>#{base}#{path}</loc>\n" <>
|
||||
" <changefreq>#{changefreq}</changefreq>\n" <>
|
||||
" <priority>#{priority}</priority>\n" <>
|
||||
" </url>"
|
||||
Enum.map_join(all_pages, "\n", fn {path, changefreq, priority, images} ->
|
||||
image_tags =
|
||||
Enum.map_join(images, "\n", fn img ->
|
||||
"""
|
||||
<image:image>
|
||||
<image:loc>#{xml_escape(img.url)}</image:loc>
|
||||
<image:title>#{xml_escape(img.title)}</image:title>
|
||||
</image:image>
|
||||
"""
|
||||
|> String.trim_trailing()
|
||||
end)
|
||||
|
||||
url_content =
|
||||
" <loc>#{xml_escape(base <> path)}</loc>\n" <>
|
||||
" <changefreq>#{changefreq}</changefreq>\n" <>
|
||||
" <priority>#{priority}</priority>"
|
||||
|
||||
if image_tags == "" do
|
||||
" <url>\n#{url_content}\n </url>"
|
||||
else
|
||||
" <url>\n#{url_content}\n#{image_tags}\n </url>"
|
||||
end
|
||||
end)
|
||||
|
||||
xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
|
||||
#{entries}
|
||||
</urlset>
|
||||
"""
|
||||
@@ -76,4 +94,27 @@ defmodule BerrypodWeb.SeoController do
|
||||
|> put_resp_content_type("application/xml")
|
||||
|> send_resp(200, xml)
|
||||
end
|
||||
|
||||
defp product_image_entries(product, base_url) do
|
||||
product.images
|
||||
|> Enum.take(5)
|
||||
|> Enum.map(fn product_image ->
|
||||
image = product_image.image
|
||||
alt_text = product_image.alt_text || product.title
|
||||
url = "#{base_url}/image_cache/#{image.id}.webp"
|
||||
|
||||
%{url: url, title: alt_text}
|
||||
end)
|
||||
end
|
||||
|
||||
defp xml_escape(nil), do: ""
|
||||
|
||||
defp xml_escape(text) when is_binary(text) do
|
||||
text
|
||||
|> String.replace("&", "&")
|
||||
|> String.replace("<", "<")
|
||||
|> String.replace(">", ">")
|
||||
|> String.replace("\"", """)
|
||||
|> String.replace("'", "'")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user