diff --git a/www/astro.config.mjs b/www/astro.config.mjs index d3fc86a..99e83c9 100644 --- a/www/astro.config.mjs +++ b/www/astro.config.mjs @@ -10,9 +10,7 @@ export default defineConfig({ compressHTML: true, trailingSlash: 'ignore', integrations: [ - sitemap({ - lastmod: new Date(), - }), + sitemap(), react(), ], vite: { diff --git a/www/public/_redirects b/www/public/_redirects new file mode 100644 index 0000000..aa33418 --- /dev/null +++ b/www/public/_redirects @@ -0,0 +1 @@ +/* /404.html 404 diff --git a/www/public/apple-touch-icon.png b/www/public/apple-touch-icon.png new file mode 100644 index 0000000..9569c8d Binary files /dev/null and b/www/public/apple-touch-icon.png differ diff --git a/www/public/site.webmanifest b/www/public/site.webmanifest index a84ed6b..4389cc8 100644 --- a/www/public/site.webmanifest +++ b/www/public/site.webmanifest @@ -12,6 +12,12 @@ "sizes": "any", "type": "image/svg+xml", "purpose": "any" + }, + { + "src": "/apple-touch-icon.png", + "sizes": "180x180", + "type": "image/png", + "purpose": "any" } ] } diff --git a/www/src/layouts/Layout.astro b/www/src/layouts/Layout.astro index 82e8d05..fb18161 100644 --- a/www/src/layouts/Layout.astro +++ b/www/src/layouts/Layout.astro @@ -36,8 +36,13 @@ const isProd = import.meta.env.PROD; - + + + + + + @@ -54,6 +59,7 @@ const isProd = import.meta.env.PROD; + diff --git a/www/src/lib/seo.test.ts b/www/src/lib/seo.test.ts index b3886a7..1d1e516 100644 --- a/www/src/lib/seo.test.ts +++ b/www/src/lib/seo.test.ts @@ -6,6 +6,7 @@ import { buildWebSiteJsonLd, buildBreadcrumbJsonLd, buildTitle, + buildProductJsonLd, } from "./seo"; describe("seo builders", () => { @@ -70,4 +71,18 @@ describe("seo builders", () => { it("buildTitle appends the brand", () => { expect(buildTitle("PostgreSQL GUI")).toBe("PostgreSQL GUI | Gridline"); }); + + it("buildProductJsonLd returns valid Product schema", () => { + const ld = buildProductJsonLd({ + name: "Gridline", + url: "https://getgridline.app/", + description: "Open-source database GUI", + image: "https://getgridline.app/og.png", + offersPrice: 0, + offersCurrency: "USD", + }); + expect(ld["@type"]).toBe("Product"); + expect(ld.name).toBe("Gridline"); + expect((ld.offers as { price: number }).price).toBe(0); + }); }); diff --git a/www/src/lib/seo.ts b/www/src/lib/seo.ts index e7c9994..7c9fee5 100644 --- a/www/src/lib/seo.ts +++ b/www/src/lib/seo.ts @@ -108,3 +108,26 @@ export function buildBreadcrumbJsonLd( export function buildTitle(primary: string, brand = "Gridline"): string { return `${primary} | ${brand}`; } + +export function buildProductJsonLd(opts: { + name: string; + url: string; + description: string; + image: string; + offersPrice: number; + offersCurrency: string; +}): JsonLd { + return { + "@context": "https://schema.org", + "@type": "Product", + name: opts.name, + url: opts.url, + description: opts.description, + image: opts.image, + offers: { + "@type": "Offer", + price: opts.offersPrice, + priceCurrency: opts.offersCurrency, + }, + }; +} diff --git a/www/src/pages/index.astro b/www/src/pages/index.astro index 431614e..82f3fc8 100644 --- a/www/src/pages/index.astro +++ b/www/src/pages/index.astro @@ -6,6 +6,8 @@ import { buildFaqJsonLd, buildOrganizationJsonLd, buildWebSiteJsonLd, + buildProductJsonLd, + buildBreadcrumbJsonLd, } from "../lib/seo"; import { Image } from "astro:assets"; import HeroDbViewer from "../components/hero/HeroDbViewer"; @@ -42,9 +44,21 @@ const jsonLd = [ name: "Gridline", url: `${site}/`, }), + buildProductJsonLd({ + name: "Gridline", + url: `${site}/`, + description: + "The open-source database GUI for PostgreSQL, MySQL, SQLite, and Redis. No paywalls, no tab limits, fully open source.", + image: `${site}/og.png`, + offersPrice: 0, + offersCurrency: "USD", + }), + buildBreadcrumbJsonLd([ + { name: "Home", url: `${site}/` }, + ]), ]; --- - +