mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 09:36:08 +00:00
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:
+92
-3
@@ -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"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
import { seoConfig } from '@/lib/seo';
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: `${seoConfig.name} - ${seoConfig.title}`,
|
||||
short_name: seoConfig.name,
|
||||
description: seoConfig.description,
|
||||
start_url: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#0a0a0a',
|
||||
theme_color: '#0a0a0a',
|
||||
icons: [
|
||||
{
|
||||
src: seoConfig.logo,
|
||||
sizes: 'any',
|
||||
type: 'image/svg+xml',
|
||||
purpose: 'any',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
import { seoConfig } from '@/lib/seo';
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: [
|
||||
{
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
disallow: ['/api/', '/admin', '/dashboard', '/projects', '/settings', '/workspaces'],
|
||||
},
|
||||
],
|
||||
sitemap: `${seoConfig.url}/sitemap.xml`,
|
||||
host: seoConfig.url,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
import { seoConfig } from '@/lib/seo';
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const lastModified = new Date();
|
||||
|
||||
return [
|
||||
{
|
||||
url: seoConfig.url,
|
||||
lastModified,
|
||||
changeFrequency: 'daily',
|
||||
priority: 1,
|
||||
},
|
||||
{
|
||||
url: `${seoConfig.url}/login`,
|
||||
lastModified,
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.6,
|
||||
},
|
||||
{
|
||||
url: `${seoConfig.url}/register`,
|
||||
lastModified,
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.7,
|
||||
},
|
||||
];
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
const FALLBACK_SITE_URL = 'https://openframe.net';
|
||||
|
||||
function normalizeSiteUrl(rawUrl: string | undefined): string {
|
||||
if (!rawUrl) {
|
||||
return FALLBACK_SITE_URL;
|
||||
}
|
||||
|
||||
const trimmed = rawUrl.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
return FALLBACK_SITE_URL;
|
||||
}
|
||||
|
||||
const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
||||
|
||||
try {
|
||||
return new URL(withProtocol).origin;
|
||||
} catch {
|
||||
return FALLBACK_SITE_URL;
|
||||
}
|
||||
}
|
||||
|
||||
export function getSiteUrl(): string {
|
||||
return normalizeSiteUrl(process.env.NEXT_PUBLIC_APP_URL ?? process.env.NEXTAUTH_URL);
|
||||
}
|
||||
|
||||
export const seoConfig = {
|
||||
name: 'OpenFrame',
|
||||
title: 'Open Source Video Review Platform',
|
||||
description:
|
||||
'OpenFrame is an open source video review platform for collecting timestamped feedback with text and voice comments.',
|
||||
keywords: [
|
||||
'open source video review platform',
|
||||
'video review tool',
|
||||
'timestamped video feedback',
|
||||
'video collaboration',
|
||||
'video annotation',
|
||||
'creative review workflow',
|
||||
],
|
||||
url: getSiteUrl(),
|
||||
ogImage: '/meta.webp',
|
||||
logoPath: '/icon.svg',
|
||||
logo: '/icon.svg?v=2',
|
||||
githubUrl: 'https://github.com/yusufipk/OpenFrame',
|
||||
} as const;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Reference in New Issue
Block a user