open-prompt / src /app /layout.tsx
anky2002's picture
feat: integrate service worker + env validation + Web Vitals in layout
9f93ce6 verified
import type { Metadata } from "next"
import "./globals.css"
import { Suspense } from "react"
import { Inter, Playfair_Display, JetBrains_Mono } from "next/font/google"
import { ThemeProvider } from "@/components/providers/theme-provider"
import { AuthProvider } from "@/components/providers/auth-provider"
import { ServiceWorkerRegistration } from "@/components/providers/service-worker"
import { OllamaProvider } from "@/contexts/ollama-context"
import { Header } from "@/components/layout/header"
import { Footer } from "@/components/layout/footer"
import { KeyboardShortcuts } from "@/components/layout/keyboard-shortcuts"
import { OnboardingTour } from "@/components/onboarding/onboarding-tour"
import { generateWebsiteSchema, generateOrganizationSchema, generateSoftwareApplicationSchema } from "@/lib/seo"
// Validate environment on import (dev only)
import "@/lib/env"
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
display: "swap",
})
const playfair = Playfair_Display({
subsets: ["latin"],
variable: "--font-playfair",
display: "swap",
})
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains",
display: "swap",
})
const BASE_URL = process.env.NEXT_PUBLIC_APP_URL || 'https://open-prompt.netlify.app'
export const metadata: Metadata = {
metadataBase: new URL(BASE_URL),
title: {
default: "OpenPrompt | The GitHub for AI Prompts",
template: "%s | OpenPrompt",
},
description: "Transform AI prompts into shareable micro-apps. Create, remix, and run prompts with zero friction. 177+ AI tools, multi-model support, and browser extension.",
keywords: [
"AI prompts",
"prompt engineering",
"ChatGPT prompts",
"Claude prompts",
"Gemini prompts",
"AI tools",
"prompt marketplace",
"micro-apps",
"GPT-4",
"prompt optimizer",
"AI writing tools",
"prompt templates",
"Midjourney prompts",
"DALL-E prompts",
"Stable Diffusion prompts",
],
authors: [{ name: "Anky9972", url: "https://github.com/Anky9972" }],
creator: "OpenPrompt",
publisher: "OpenPrompt",
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-video-preview": -1,
"max-image-preview": "large",
"max-snippet": -1,
},
},
openGraph: {
title: "OpenPrompt | The GitHub for AI Prompts",
description: "Transform AI prompts into shareable micro-apps. 177+ AI tools, multi-model support, and browser extension.",
url: BASE_URL,
siteName: "OpenPrompt",
images: [
{
url: `${BASE_URL}/api/og?title=OpenPrompt&description=The+GitHub+for+AI+Prompts`,
width: 1200,
height: 630,
alt: "OpenPrompt - The GitHub for AI Prompts",
},
],
locale: "en_US",
type: "website",
},
twitter: {
card: "summary_large_image",
title: "OpenPrompt | The GitHub for AI Prompts",
description: "Transform AI prompts into shareable micro-apps. 177+ AI tools, multi-model support.",
creator: "@anky_vivek",
site: "@openprompt",
images: [`${BASE_URL}/api/og?title=OpenPrompt&description=The+GitHub+for+AI+Prompts`],
},
icons: {
icon: [
{ url: "/logos/logo.svg", type: "image/svg+xml" },
{ url: "/favicon.ico", sizes: "32x32" },
],
shortcut: "/logos/logo.svg",
apple: [
{ url: "/logos/icon.png", sizes: "180x180" },
],
other: [
{
rel: "apple-touch-icon-precomposed",
url: "/logos/icon.png",
},
{
rel: "mask-icon",
url: "/logos/logo.svg",
color: "#6366f1",
},
],
},
manifest: "/manifest.json",
alternates: {
canonical: BASE_URL,
},
verification: {
google: "google60f35921954050fd",
},
category: "technology",
classification: "AI Tools, Productivity, Developer Tools",
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
const websiteSchema = generateWebsiteSchema()
const organizationSchema = generateOrganizationSchema()
const softwareSchema = generateSoftwareApplicationSchema()
return (
<html lang="en" className={`${inter.variable} ${playfair.variable} ${jetbrainsMono.variable}`} suppressHydrationWarning>
<head>
{/* JSON-LD Structured Data */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(softwareSchema) }}
/>
{/* Preconnect to external services for faster loading */}
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
{/* DNS prefetch for AI API endpoints */}
<link rel="dns-prefetch" href="https://api.openai.com" />
<link rel="dns-prefetch" href="https://api.anthropic.com" />
<link rel="dns-prefetch" href="https://generativelanguage.googleapis.com" />
</head>
<body className="min-h-screen bg-background antialiased" suppressHydrationWarning>
<AuthProvider>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
<OllamaProvider>
<Suspense fallback={
<div className="relative flex min-h-screen flex-col">
<div className="h-16 border-b border-border" />
<main className="flex-1 flex items-center justify-center">
<div className="h-8 w-8 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</main>
</div>
}>
<div className="relative flex min-h-screen flex-col">
<Header />
<main className="flex-1">{children}</main>
<Footer />
<KeyboardShortcuts />
<OnboardingTour />
</div>
</Suspense>
{/* PWA Service Worker Registration */}
<ServiceWorkerRegistration />
</OllamaProvider>
</ThemeProvider>
</AuthProvider>
</body>
</html>
)
}