From 0a3b3befacce967c046ba8d600597fc86e702f6c Mon Sep 17 00:00:00 2001 From: Ciucur Erol Date: Wed, 27 May 2026 15:06:24 +0300 Subject: [PATCH 1/4] Improve SEO with schema.org structured data Adds JSON-LD, richer Open Graph/Twitter metadata, and robots metadata to match claudebin.com. Co-authored-by: Cursor --- public/images/favicon.svg | 14 +++++ public/images/og-default-1200x630.svg | 44 ++++++++++++++++ src/app/layout.tsx | 74 ++++++++++++++++++++++++--- src/app/robots.ts | 17 ++++++ src/copy/en-EN.json | 9 ++++ 5 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 public/images/favicon.svg create mode 100644 public/images/og-default-1200x630.svg create mode 100644 src/app/robots.ts diff --git a/public/images/favicon.svg b/public/images/favicon.svg new file mode 100644 index 0000000..cfd27e8 --- /dev/null +++ b/public/images/favicon.svg @@ -0,0 +1,14 @@ + + + + + diff --git a/public/images/og-default-1200x630.svg b/public/images/og-default-1200x630.svg new file mode 100644 index 0000000..b711426 --- /dev/null +++ b/public/images/og-default-1200x630.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + Openable.dev + + + Run AI-generated apps without hassle + + + diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2348564..8826909 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,11 +4,23 @@ import type { Metadata } from "next"; import { NextIntlClientProvider } from "next-intl"; import { getLocale, getMessages, getTranslations } from "next-intl/server"; +import copy from "@/copy/en-EN.json"; import { HomePageAppBar } from "@/components/home-page-app-bar"; import { HomePageFooter } from "@/components/home-page-footer"; +import { toString } from "es-toolkit/compat"; const SITE_URL = "https://openable.dev"; +const OG_IMAGE_WIDTH = 1200; +const OG_IMAGE_HEIGHT = 630; + +const openGraphImage = { + url: "/images/og-default-1200x630.svg", + width: OG_IMAGE_WIDTH, + height: OG_IMAGE_HEIGHT, + alt: copy.metadata.siteName, +}; + export const generateMetadata = async (): Promise => { const t = await getTranslations(); @@ -19,28 +31,68 @@ export const generateMetadata = async (): Promise => { alternates: { canonical: "/", }, + icons: "/images/favicon.svg", openGraph: { type: "website", locale: "en_US", url: SITE_URL, - siteName: t("metadata.title"), + siteName: copy.metadata.siteName, title: t("metadata.ogTitle"), description: t("metadata.ogDescription"), + images: [openGraphImage], }, twitter: { card: "summary_large_image", title: t("metadata.twitterTitle"), description: t("metadata.twitterDescription"), + images: [openGraphImage], }, - robots: { - index: true, - follow: true, - googleBot: { - index: true, - follow: true, + }; +}; + +const data = { + "@context": "https://schema.org", + "@graph": [ + { + "@type": "Organization", + name: copy.metadata.organizationName, + email: copy.metadata.organizationEmail, + url: copy.metadata.organizationUrl, + sameAs: copy.metadata.organizationSameAs, + logo: toString(new URL("/images/favicon.svg", SITE_URL)), + }, + { + "@type": "WebSite", + name: copy.metadata.siteName, + description: copy.metadata.description, + url: toString(new URL("/", SITE_URL)), + inLanguage: "en-US", + publisher: { + "@type": "Organization", + name: copy.metadata.organizationName, + url: copy.metadata.organizationUrl, }, }, - }; + { + "@type": "SoftwareApplication", + name: copy.metadata.siteName, + description: copy.metadata.description, + applicationCategory: "DeveloperApplication", + operatingSystem: "Web", + url: toString(new URL("/", SITE_URL)), + image: toString(new URL(openGraphImage.url, SITE_URL)), + offers: { + "@type": "Offer", + price: "0", + priceCurrency: "USD", + }, + publisher: { + "@type": "Organization", + name: copy.metadata.organizationName, + url: copy.metadata.organizationUrl, + }, + }, + ], }; const RootLayout = async ({ @@ -58,6 +110,12 @@ const RootLayout = async ({ > +