From 323093029ef2757bb3cddd9278564b49823ee1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0pek?= Date: Sun, 22 Feb 2026 16:53:01 +0300 Subject: [PATCH] feat(seo): upgrade site metadata, schema, and crawl files for OpenFrame rebrand - update title/description to Open Source Video Review Platform - add centralized SEO config with canonical URL + meta image (/meta.webp) - add Open Graph and Twitter card metadata - add JSON-LD Organization/WebSite structured data - add robots.txt, sitemap.xml, and web app manifest routes - version icon metadata to help search engines refresh branding --- app/layout.tsx | 95 +++++++++++++++++++++++++++++++++++++++++++++-- app/manifest.ts | 22 +++++++++++ app/robots.ts | 16 ++++++++ app/sitemap.ts | 27 ++++++++++++++ lib/seo.ts | 45 ++++++++++++++++++++++ public/meta.webp | Bin 0 -> 65462 bytes 6 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 app/manifest.ts create mode 100644 app/robots.ts create mode 100644 app/sitemap.ts create mode 100644 lib/seo.ts create mode 100644 public/meta.webp diff --git a/app/layout.tsx b/app/layout.tsx index dbb5802..e9261cb 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,7 +1,8 @@ import type { Metadata } from "next"; import { Geist_Mono, JetBrains_Mono } from "next/font/google"; -import { ThemeProvider } from "@/components/theme-provider"; import { Toaster } from "sonner"; +import { ThemeProvider } from "@/components/theme-provider"; +import { seoConfig } from "@/lib/seo"; import "./globals.css"; const jetbrainsMono = JetBrains_Mono({ @@ -19,10 +20,94 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "OpenFrame - Video Feedback Platform", - description: "Collect timestamped video feedback with text and voice comments", + metadataBase: new URL(seoConfig.url), + title: { + default: `${seoConfig.name} | ${seoConfig.title}`, + template: `%s | ${seoConfig.name}`, + }, + description: seoConfig.description, + applicationName: seoConfig.name, + keywords: [...seoConfig.keywords], + authors: [{ name: seoConfig.name, url: seoConfig.url }], + creator: seoConfig.name, + publisher: seoConfig.name, + category: "technology", + referrer: "origin-when-cross-origin", + alternates: { + canonical: "/", + }, + icons: { + icon: [{ url: seoConfig.logo, type: "image/svg+xml" }], + shortcut: [seoConfig.logo], + apple: [{ url: seoConfig.logo }], + }, + manifest: "/manifest.webmanifest", + openGraph: { + type: "website", + locale: "en_US", + siteName: seoConfig.name, + url: seoConfig.url, + title: `${seoConfig.name} | ${seoConfig.title}`, + description: seoConfig.description, + images: [ + { + url: seoConfig.ogImage, + width: 1888, + height: 1048, + alt: `${seoConfig.name} meta image`, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: `${seoConfig.name} | ${seoConfig.title}`, + description: seoConfig.description, + images: [seoConfig.ogImage], + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-image-preview": "large", + "max-snippet": -1, + "max-video-preview": -1, + }, + }, + formatDetection: { + email: false, + address: false, + telephone: false, + }, }; +const structuredData = [ + { + "@context": "https://schema.org", + "@type": "Organization", + name: seoConfig.name, + url: seoConfig.url, + logo: `${seoConfig.url}${seoConfig.logoPath}`, + sameAs: [seoConfig.githubUrl], + }, + { + "@context": "https://schema.org", + "@type": "WebSite", + name: seoConfig.name, + url: seoConfig.url, + description: seoConfig.description, + publisher: { + "@type": "Organization", + name: seoConfig.name, + logo: { + "@type": "ImageObject", + url: `${seoConfig.url}${seoConfig.logoPath}`, + }, + }, + }, +]; + export default function RootLayout({ children, }: Readonly<{ @@ -31,6 +116,10 @@ export default function RootLayout({ return ( +