diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png index 09c8324..cf40561 100644 Binary files a/public/android-chrome-192x192.png and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png index 11d626e..3e39438 100644 Binary files a/public/android-chrome-512x512.png and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png index 5a9423c..fe3bb8c 100644 Binary files a/public/apple-touch-icon.png and b/public/apple-touch-icon.png differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png index e3389b0..7fbaf31 100644 Binary files a/public/favicon-16x16.png and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png index 900c77d..4463802 100644 Binary files a/public/favicon-32x32.png and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico index 1a17516..1495fdd 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/site.webmanifest b/public/site.webmanifest index fa99de7..63f961f 100644 --- a/public/site.webmanifest +++ b/public/site.webmanifest @@ -1,6 +1,6 @@ { - "name": "", - "short_name": "", + "name": "OpenSEO", + "short_name": "OpenSEO", "icons": [ { "src": "/android-chrome-192x192.png", @@ -13,7 +13,7 @@ "type": "image/png" } ], - "theme_color": "#ffffff", - "background_color": "#ffffff", + "theme_color": "#0a0f0d", + "background_color": "#0a0f0d", "display": "standalone" } diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index c8a1749..8ec4a99 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -56,7 +56,8 @@ export const Route = createRootRoute({ sizes: "16x16", href: "/favicon-16x16.png", }, - { rel: "icon", href: "/favicon.ico" }, + { rel: "icon", type: "image/x-icon", href: "/favicon.ico" }, + { rel: "manifest", href: "/site.webmanifest" }, ], scripts: [], }), diff --git a/web/public/android-chrome-192x192.png b/web/public/android-chrome-192x192.png new file mode 100644 index 0000000..cf40561 Binary files /dev/null and b/web/public/android-chrome-192x192.png differ diff --git a/web/public/android-chrome-512x512.png b/web/public/android-chrome-512x512.png new file mode 100644 index 0000000..3e39438 Binary files /dev/null and b/web/public/android-chrome-512x512.png differ diff --git a/web/public/apple-touch-icon.png b/web/public/apple-touch-icon.png new file mode 100644 index 0000000..fe3bb8c Binary files /dev/null and b/web/public/apple-touch-icon.png differ diff --git a/web/public/favicon-16x16.png b/web/public/favicon-16x16.png new file mode 100644 index 0000000..7fbaf31 Binary files /dev/null and b/web/public/favicon-16x16.png differ diff --git a/web/public/favicon-32x32.png b/web/public/favicon-32x32.png new file mode 100644 index 0000000..4463802 Binary files /dev/null and b/web/public/favicon-32x32.png differ diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000..1495fdd Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/web/public/site.webmanifest b/web/public/site.webmanifest new file mode 100644 index 0000000..63f961f --- /dev/null +++ b/web/public/site.webmanifest @@ -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" +} diff --git a/web/public/social-card.png b/web/public/social-card.png new file mode 100644 index 0000000..57e6527 Binary files /dev/null and b/web/public/social-card.png differ diff --git a/web/src/lib/seo.ts b/web/src/lib/seo.ts index a315162..2f08399 100644 --- a/web/src/lib/seo.ts +++ b/web/src/lib/seo.ts @@ -1,4 +1,6 @@ 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 = ( process.env.SITE_URL ?? @@ -23,6 +25,8 @@ type BuildSeoParams = { description?: string; titleSuffix?: string; ogType?: "website" | "article"; + imagePath?: string; + imageAlt?: string; }; export function buildPageSeo({ @@ -31,20 +35,34 @@ export function buildPageSeo({ description, titleSuffix, ogType = "website", + imagePath = DEFAULT_SOCIAL_IMAGE_PATH, + imageAlt = DEFAULT_SOCIAL_IMAGE_ALT, }: BuildSeoParams) { const fullTitle = titleSuffix ? `${title} - ${titleSuffix}` : title; const canonicalUrl = toCanonicalUrl(path); + const socialImageUrl = toCanonicalUrl(imagePath); return { meta: [ { title: fullTitle }, ...(description ? [{ name: "description", content: description }] : []), + { property: "og:site_name", content: "OpenSEO" }, { property: "og:type", content: ogType }, { property: "og:title", content: fullTitle }, ...(description ? [{ property: "og:description", content: description }] : []), { 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 }], }; diff --git a/web/src/routes/__root.tsx b/web/src/routes/__root.tsx index 6fafce6..97cca9d 100644 --- a/web/src/routes/__root.tsx +++ b/web/src/routes/__root.tsx @@ -22,6 +22,24 @@ export const Route = createRootRoute({ links: [ { rel: "stylesheet", href: appCss }, { 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, diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index d4e4f9e..27b88d9 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -1,26 +1,19 @@ import { createFileRoute, Link } from "@tanstack/react-router"; import { useState } from "react"; -import { toCanonicalUrl } from "@/lib/seo"; +import { buildPageSeo } from "@/lib/seo"; const homeTitle = "OpenSEO - Open Source SEO Platform"; const homeDescription = "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("/")({ - head: () => ({ - meta: [ - { title: homeTitle }, - { name: "description", content: homeDescription }, - { property: "og:type", content: "website" }, - { property: "og:title", content: homeTitle }, - { 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("/") }], - }), + head: () => + buildPageSeo({ + title: homeTitle, + description: homeDescription, + path: "/", + imageAlt: "OpenSEO keyword research dashboard preview", + }), component: Home, });