fix: improve social previews and shared icons (#52)

Add share metadata and a real product social card so OpenSEO links render with a stronger preview. Sync the landing site and app shell favicons with the shared Every App icon set.
This commit is contained in:
Ben Senescu 2026-03-27 18:56:14 -04:00 committed by GitHub
parent cdd729ec0d
commit 191879c87c
19 changed files with 69 additions and 20 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 832 B

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,6 +1,6 @@
{ {
"name": "", "name": "OpenSEO",
"short_name": "", "short_name": "OpenSEO",
"icons": [ "icons": [
{ {
"src": "/android-chrome-192x192.png", "src": "/android-chrome-192x192.png",
@ -13,7 +13,7 @@
"type": "image/png" "type": "image/png"
} }
], ],
"theme_color": "#ffffff", "theme_color": "#0a0f0d",
"background_color": "#ffffff", "background_color": "#0a0f0d",
"display": "standalone" "display": "standalone"
} }

View File

@ -56,7 +56,8 @@ export const Route = createRootRoute({
sizes: "16x16", sizes: "16x16",
href: "/favicon-16x16.png", href: "/favicon-16x16.png",
}, },
{ rel: "icon", href: "/favicon.ico" }, { rel: "icon", type: "image/x-icon", href: "/favicon.ico" },
{ rel: "manifest", href: "/site.webmanifest" },
], ],
scripts: [], scripts: [],
}), }),

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,19 @@
{
"name": "OpenSEO",
"short_name": "OpenSEO",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#0a0f0d",
"background_color": "#0a0f0d",
"display": "standalone"
}

BIN
web/public/social-card.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View File

@ -1,4 +1,6 @@
const DEFAULT_SITE_URL = "https://openseo.so"; const DEFAULT_SITE_URL = "https://openseo.so";
const DEFAULT_SOCIAL_IMAGE_PATH = "/social-card.png";
const DEFAULT_SOCIAL_IMAGE_ALT = "OpenSEO product preview";
export const SITE_URL = ( export const SITE_URL = (
process.env.SITE_URL ?? process.env.SITE_URL ??
@ -23,6 +25,8 @@ type BuildSeoParams = {
description?: string; description?: string;
titleSuffix?: string; titleSuffix?: string;
ogType?: "website" | "article"; ogType?: "website" | "article";
imagePath?: string;
imageAlt?: string;
}; };
export function buildPageSeo({ export function buildPageSeo({
@ -31,20 +35,34 @@ export function buildPageSeo({
description, description,
titleSuffix, titleSuffix,
ogType = "website", ogType = "website",
imagePath = DEFAULT_SOCIAL_IMAGE_PATH,
imageAlt = DEFAULT_SOCIAL_IMAGE_ALT,
}: BuildSeoParams) { }: BuildSeoParams) {
const fullTitle = titleSuffix ? `${title} - ${titleSuffix}` : title; const fullTitle = titleSuffix ? `${title} - ${titleSuffix}` : title;
const canonicalUrl = toCanonicalUrl(path); const canonicalUrl = toCanonicalUrl(path);
const socialImageUrl = toCanonicalUrl(imagePath);
return { return {
meta: [ meta: [
{ title: fullTitle }, { title: fullTitle },
...(description ? [{ name: "description", content: description }] : []), ...(description ? [{ name: "description", content: description }] : []),
{ property: "og:site_name", content: "OpenSEO" },
{ property: "og:type", content: ogType }, { property: "og:type", content: ogType },
{ property: "og:title", content: fullTitle }, { property: "og:title", content: fullTitle },
...(description ...(description
? [{ property: "og:description", content: description }] ? [{ property: "og:description", content: description }]
: []), : []),
{ property: "og:url", content: canonicalUrl }, { property: "og:url", content: canonicalUrl },
{ property: "og:image", content: socialImageUrl },
{ property: "og:image:alt", content: imageAlt },
{ property: "og:image:type", content: "image/png" },
{ property: "og:image:width", content: "1200" },
{ property: "og:image:height", content: "630" },
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:title", content: fullTitle },
...(description ? [{ name: "twitter:description", content: description }] : []),
{ name: "twitter:image", content: socialImageUrl },
{ name: "twitter:image:alt", content: imageAlt },
], ],
links: [{ rel: "canonical", href: canonicalUrl }], links: [{ rel: "canonical", href: canonicalUrl }],
}; };

View File

@ -22,6 +22,24 @@ export const Route = createRootRoute({
links: [ links: [
{ rel: "stylesheet", href: appCss }, { rel: "stylesheet", href: appCss },
{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }, { rel: "icon", type: "image/x-icon", href: "/favicon.ico" },
{
rel: "apple-touch-icon",
sizes: "180x180",
href: "/apple-touch-icon.png",
},
{
rel: "icon",
type: "image/png",
sizes: "32x32",
href: "/favicon-32x32.png",
},
{
rel: "icon",
type: "image/png",
sizes: "16x16",
href: "/favicon-16x16.png",
},
{ rel: "manifest", href: "/site.webmanifest" },
], ],
}), }),
component: RootComponent, component: RootComponent,

View File

@ -1,25 +1,18 @@
import { createFileRoute, Link } from "@tanstack/react-router"; import { createFileRoute, Link } from "@tanstack/react-router";
import { useState } from "react"; import { useState } from "react";
import { toCanonicalUrl } from "@/lib/seo"; import { buildPageSeo } from "@/lib/seo";
const homeTitle = "OpenSEO - Open Source SEO Platform"; const homeTitle = "OpenSEO - Open Source SEO Platform";
const homeDescription = const homeDescription =
"Own your SEO. OpenSEO helps teams manage keyword research, backlink analysis, competitor monitoring, and site audits without expensive monthly SEO software subscriptions."; "Own your SEO. OpenSEO helps teams manage keyword research, backlink analysis, competitor monitoring, and site audits without expensive monthly SEO software subscriptions.";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
head: () => ({ head: () =>
meta: [ buildPageSeo({
{ title: homeTitle }, title: homeTitle,
{ name: "description", content: homeDescription }, description: homeDescription,
{ property: "og:type", content: "website" }, path: "/",
{ property: "og:title", content: homeTitle }, imageAlt: "OpenSEO keyword research dashboard preview",
{ property: "og:description", content: homeDescription },
{ property: "og:url", content: toCanonicalUrl("/") },
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:title", content: homeTitle },
{ name: "twitter:description", content: homeDescription },
],
links: [{ rel: "canonical", href: toCanonicalUrl("/") }],
}), }),
component: Home, component: Home,
}); });