development
tsx 128 lines 3.4 KB
Raw
1 import type { Metadata } from "next";
2 import "./globals.css";
3 import { Inter } from "next/font/google";
4 import { ThemeProvider } from "@/components/theme-provider";
5 import Footer from "@/components/footer";
6 import Header from "@/components/header";
7 import { StructuredData } from "@/components/structured-data";
8 import siteMetadata from "@/content/siteMetadata";
9 import { absoluteUrl } from "./seo";
10
11 const inter = Inter({
12 subsets: ["latin"],
13 variable: "--font-inter",
14 display: "swap",
15 });
16
17 export const metadata: Metadata = {
18 metadataBase: new URL(siteMetadata.siteUrl),
19 applicationName: siteMetadata.title,
20 title: {
21 default: siteMetadata.title,
22 template: `%s | ${siteMetadata.title}`,
23 },
24 description: siteMetadata.description,
25 keywords: siteMetadata.keywords,
26 authors: [{ name: siteMetadata.author }],
27 creator: siteMetadata.author,
28 publisher: siteMetadata.companyName,
29 category: "Developer tools",
30 alternates: {
31 canonical: "/",
32 },
33 openGraph: {
34 title: siteMetadata.title,
35 description: siteMetadata.description,
36 url: siteMetadata.siteUrl,
37 siteName: siteMetadata.title,
38 locale: "en_US",
39 type: "website",
40 images: [
41 {
42 url: siteMetadata.socialBanner || "/twitter-card.png",
43 alt: siteMetadata.tagline,
44 },
45 ],
46 },
47 twitter: {
48 card: "summary_large_image",
49 title: siteMetadata.title,
50 description: siteMetadata.description,
51 images: [siteMetadata.socialBanner || "/twitter-card.png"],
52 },
53 robots: {
54 index: true,
55 follow: true,
56 googleBot: {
57 index: true,
58 follow: true,
59 "max-image-preview": "large",
60 "max-snippet": -1,
61 "max-video-preview": -1,
62 },
63 },
64 icons: {
65 icon: [{ url: "/favicon.ico" }],
66 shortcut: [{ url: "/favicon.ico" }],
67 },
68 };
69
70 const globalStructuredData = {
71 "@context": "https://schema.org",
72 "@graph": [
73 {
74 "@type": "Organization",
75 "@id": absoluteUrl("/#organization"),
76 name: siteMetadata.organizationName,
77 legalName: siteMetadata.companyName,
78 url: siteMetadata.siteUrl,
79 logo: absoluteUrl(siteMetadata.siteLogo),
80 email: siteMetadata.email,
81 sameAs: [
82 siteMetadata.github,
83 siteMetadata.facebook,
84 siteMetadata.instagram,
85 siteMetadata.youtube,
86 ].filter(Boolean),
87 address: {
88 "@type": "PostalAddress",
89 addressLocality: "Stockholm",
90 addressCountry: "SE",
91 },
92 },
93 {
94 "@type": "WebSite",
95 "@id": absoluteUrl("/#website"),
96 url: siteMetadata.siteUrl,
97 name: siteMetadata.title,
98 description: siteMetadata.description,
99 inLanguage: siteMetadata.language,
100 publisher: {
101 "@id": absoluteUrl("/#organization"),
102 },
103 },
104 ],
105 };
106
107 interface RootLayoutProps {
108 children: React.ReactNode;
109 }
110
111 export default function RootLayout({ children }: RootLayoutProps) {
112 return (
113 <html lang="en" suppressHydrationWarning>
114 <body
115 className={`${inter.variable} font-sans antialiased min-h-screen bg-white dark:bg-[#0A0E14] text-slate-900 dark:text-slate-50`}
116 >
117 <StructuredData data={globalStructuredData} />
118 <ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
119 <div className="flex min-h-screen flex-col">
120 <Header />
121 <main className="flex-1">{children}</main>
122 <Footer />
123 </div>
124 </ThemeProvider>
125 </body>
126 </html>
127 );
128 }