From db417e2b838e8051e0fce88cf88905f02903dd38 Mon Sep 17 00:00:00 2001 From: Fabrizio Salmi Date: Sat, 23 May 2026 08:13:40 +0200 Subject: [PATCH] feat(seo): bilingual how-to guides section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a /guides hub plus 4 how-to guides (EN + IT) targeting informational search intent and funneling to the matching client-side tool: - How to decode an SSL certificate -> Certificate Decoder - How to read a CSR -> CSR Decoder - How to check a private key matches a certificate -> Key Matcher - How to build a certificate chain -> Chain Builder Content lives in structured TS data (no MDX), rendered through the existing Layout so it stays inside the strict CSP (no inline scripts, no network). Routes for EN (/guides/...) and IT (/it/guides/...), sitemap updated, and a Guides link added to the header. Full hardened build passes: check-no-innerhtml, astro build, inject-csp (26 HTML files), check-no-network — all green. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/GuideArticle.astro | 51 ++++ src/components/GuidesIndex.astro | 40 +++ src/components/Header.astro | 11 + src/data/guides.ts | 278 ++++++++++++++++++++ src/pages/[lang]/guides/[guide]/index.astro | 16 ++ src/pages/[lang]/guides/index.astro | 13 + src/pages/guides/[guide]/index.astro | 12 + src/pages/guides/index.astro | 5 + src/pages/sitemap.xml.ts | 8 +- 9 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 src/components/GuideArticle.astro create mode 100644 src/components/GuidesIndex.astro create mode 100644 src/data/guides.ts create mode 100644 src/pages/[lang]/guides/[guide]/index.astro create mode 100644 src/pages/[lang]/guides/index.astro create mode 100644 src/pages/guides/[guide]/index.astro create mode 100644 src/pages/guides/index.astro diff --git a/src/components/GuideArticle.astro b/src/components/GuideArticle.astro new file mode 100644 index 0000000..285fe40 --- /dev/null +++ b/src/components/GuideArticle.astro @@ -0,0 +1,51 @@ +--- +import Layout from "~/layouts/Layout.astro"; +import { guides, guideContent } from "~/data/guides"; +import { localizedPath, type Locale } from "~/i18n"; + +interface Props { + slug: string; + locale: Locale; +} + +const { slug, locale } = Astro.props; +const guide = guides.find((g) => g.slug === slug)!; +const c = guideContent(guide, locale); +const toolHref = localizedPath(locale, `/${guide.toolSlug}/`); +const guidesHref = localizedPath(locale, "/guides/"); +const backLabel = locale === "it" ? "Tutte le guide" : "All guides"; +--- + + +
+ + +

{c.title}

+

{c.intro}

+ + {c.sections.map((s) => ( +
+

{s.heading}

+ {s.body.map((p) => ( +

{p}

+ ))} +
+ ))} + +
+ + {c.toolCta} → + +

+ {locale === "it" + ? "Tutto lato client: niente upload, niente tracciamento." + : "Runs entirely client-side: no upload, no tracking."} +

+
+
+
diff --git a/src/components/GuidesIndex.astro b/src/components/GuidesIndex.astro new file mode 100644 index 0000000..ca3e567 --- /dev/null +++ b/src/components/GuidesIndex.astro @@ -0,0 +1,40 @@ +--- +import Layout from "~/layouts/Layout.astro"; +import { guides, guideContent } from "~/data/guides"; +import { localizedPath, type Locale } from "~/i18n"; + +interface Props { + locale: Locale; +} + +const { locale } = Astro.props; +const title = locale === "it" ? "Guide TLS / SSL" : "TLS / SSL guides"; +const description = + locale === "it" + ? "Guide pratiche per decodificare certificati e CSR, verificare chiavi e costruire catene — ognuna collegata allo strumento client-side che la mette in pratica." + : "Practical how-to guides for decoding certificates and CSRs, verifying keys and building chains — each linked to the client-side tool that does it."; +--- + + +
+

{title}

+

{description}

+ + +
+
diff --git a/src/components/Header.astro b/src/components/Header.astro index a63ca6e..06d193d 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -74,6 +74,17 @@ const homeHref = localizedPath(locale, "/"); ) } + + {locale === "it" ? "Guide" : "Guides"} + + + LOCALES.filter((l) => l !== DEFAULT_LOCALE).flatMap((lang) => + guides.map((g) => ({ params: { lang, guide: g.slug } })) + ); + +const { lang, guide } = Astro.params; +const locale: Locale = isLocale(lang) ? lang : DEFAULT_LOCALE; +--- + + diff --git a/src/pages/[lang]/guides/index.astro b/src/pages/[lang]/guides/index.astro new file mode 100644 index 0000000..13dd623 --- /dev/null +++ b/src/pages/[lang]/guides/index.astro @@ -0,0 +1,13 @@ +--- +import type { GetStaticPaths } from "astro"; +import GuidesIndex from "~/components/GuidesIndex.astro"; +import { LOCALES, DEFAULT_LOCALE, isLocale, type Locale } from "~/i18n"; + +export const getStaticPaths: GetStaticPaths = () => + LOCALES.filter((l) => l !== DEFAULT_LOCALE).map((lang) => ({ params: { lang } })); + +const { lang } = Astro.params; +const locale: Locale = isLocale(lang) ? lang : DEFAULT_LOCALE; +--- + + diff --git a/src/pages/guides/[guide]/index.astro b/src/pages/guides/[guide]/index.astro new file mode 100644 index 0000000..bd8827f --- /dev/null +++ b/src/pages/guides/[guide]/index.astro @@ -0,0 +1,12 @@ +--- +import type { GetStaticPaths } from "astro"; +import GuideArticle from "~/components/GuideArticle.astro"; +import { guides } from "~/data/guides"; + +export const getStaticPaths: GetStaticPaths = () => + guides.map((g) => ({ params: { guide: g.slug } })); + +const { guide } = Astro.params; +--- + + diff --git a/src/pages/guides/index.astro b/src/pages/guides/index.astro new file mode 100644 index 0000000..2e82da4 --- /dev/null +++ b/src/pages/guides/index.astro @@ -0,0 +1,5 @@ +--- +import GuidesIndex from "~/components/GuidesIndex.astro"; +--- + + diff --git a/src/pages/sitemap.xml.ts b/src/pages/sitemap.xml.ts index de71637..6cdc796 100644 --- a/src/pages/sitemap.xml.ts +++ b/src/pages/sitemap.xml.ts @@ -1,10 +1,16 @@ import type { APIRoute } from "astro"; import { internalTools } from "~/data/tools"; +import { guides } from "~/data/guides"; import { LOCALES, DEFAULT_LOCALE, localizedPath } from "~/i18n"; const SITE = "https://tools.certmate.org"; -const routes = ["/", ...internalTools.filter((t) => t.status === "live").map((t) => `/${t.slug}/`)]; +const routes = [ + "/", + ...internalTools.filter((t) => t.status === "live").map((t) => `/${t.slug}/`), + "/guides/", + ...guides.map((g) => `/guides/${g.slug}/`), +]; function urlEntry(path: string): string { const loc = `${SITE}${localizedPath(DEFAULT_LOCALE, path)}`;