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
This commit is contained in:
Yusuf İpek
2026-02-22 16:53:01 +03:00
parent b0e6f9514a
commit 323093029e
6 changed files with 202 additions and 3 deletions
+92 -3
View File
@@ -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 (
<html lang="en" className={`${jetbrainsMono.variable} ${geistMono.variable}`} suppressHydrationWarning>
<body className="antialiased min-h-screen bg-background font-sans">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
<ThemeProvider
attribute="class"
defaultTheme="dark"