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.
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 564 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 832 B After Width: | Height: | Size: 705 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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: [],
|
||||||
}),
|
}),
|
||||||
|
|||||||
BIN
web/public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
web/public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 564 KiB |
BIN
web/public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
web/public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 705 B |
BIN
web/public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
web/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
19
web/public/site.webmanifest
Normal 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
|
After Width: | Height: | Size: 184 KiB |
@ -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 }],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -1,26 +1,19 @@
|
|||||||
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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||