From fb43cd6804a0f417c538b04e7f0ffc3d38392dac Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 5 May 2026 00:33:35 +0100 Subject: [PATCH] Improve SEO metadata --- src/app/[locale]/layout.tsx | 45 +++++++++- src/app/[locale]/opengraph-image.tsx | 120 +++++++++++++++++++++++++++ src/app/[locale]/page.tsx | 59 ++++++++++++- src/app/manifest.ts | 22 +++++ src/app/robots.ts | 16 ++++ src/app/sitemap.ts | 25 ++++++ 6 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 src/app/[locale]/opengraph-image.tsx create mode 100644 src/app/manifest.ts create mode 100644 src/app/robots.ts create mode 100644 src/app/sitemap.ts diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index c67983a..183f7a2 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -16,6 +16,7 @@ type LocaleMessages = { }; }; +const siteUrl = "https://kairas.io"; const alternates = Object.fromEntries([ ...routing.locales.map((locale) => [locale, `/${locale}`]), ["x-default", `/${routing.defaultLocale}`], @@ -41,13 +42,38 @@ export async function generateMetadata({ const messages = await getLocaleMessages(locale); return { - metadataBase: new URL("https://kairas.io"), + metadataBase: new URL(siteUrl), title: messages.Meta.title, description: messages.Meta.description, + applicationName: "Kairas", + authors: [{name: "Kairas", url: siteUrl}], + creator: "Kairas", + publisher: "Kairas", + category: "Web design", + keywords: [ + "Kairas", + "web design", + "web design studio", + "brand websites", + "design systems", + "frontend development", + "digital products", + ], alternates: { canonical: `/${locale}`, languages: alternates, }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-image-preview": "large", + "max-snippet": -1, + "max-video-preview": -1, + }, + }, openGraph: { title: messages.Meta.title, description: messages.Meta.description, @@ -55,6 +81,23 @@ export async function generateMetadata({ siteName: "Kairas", locale: locale.replace("-", "_"), type: "website", + images: [ + { + url: `/${locale}/opengraph-image`, + width: 1200, + height: 630, + alt: messages.Meta.title, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: messages.Meta.title, + description: messages.Meta.description, + images: [`/${locale}/opengraph-image`], + }, + icons: { + icon: "/icon.svg", }, }; } diff --git a/src/app/[locale]/opengraph-image.tsx b/src/app/[locale]/opengraph-image.tsx new file mode 100644 index 0000000..40e50cf --- /dev/null +++ b/src/app/[locale]/opengraph-image.tsx @@ -0,0 +1,120 @@ +import {ImageResponse} from "next/og"; +import {hasLocale} from "next-intl"; +import {localeDirections, routing, type Locale} from "@/i18n/routing"; +import arMessages from "../../../messages/ar.json"; +import bnMessages from "../../../messages/bn.json"; +import enMessages from "../../../messages/en.json"; +import esMessages from "../../../messages/es.json"; +import frMessages from "../../../messages/fr.json"; +import hiMessages from "../../../messages/hi.json"; +import ptMessages from "../../../messages/pt.json"; +import ruMessages from "../../../messages/ru.json"; +import urMessages from "../../../messages/ur.json"; +import zhMessages from "../../../messages/zh.json"; + +export const runtime = "edge"; +export const alt = "Kairas web design studio"; +export const size = { + width: 1200, + height: 630, +}; +export const contentType = "image/png"; + +type OpenGraphImageProps = { + params: Promise<{locale: string}>; +}; + +type LocaleMessages = { + Meta: { + title: string; + description: string; + }; + Home: { + hero: { + kicker: string; + words: string[]; + }; + }; +}; + +const localeMessages: Record = { + en: enMessages, + zh: zhMessages, + hi: hiMessages, + es: esMessages, + fr: frMessages, + ar: arMessages, + bn: bnMessages, + pt: ptMessages, + ru: ruMessages, + ur: urMessages, +}; + +export default async function OpenGraphImage({params}: OpenGraphImageProps) { + const {locale: requestedLocale} = await params; + const locale = hasLocale(routing.locales, requestedLocale) + ? requestedLocale + : routing.defaultLocale; + const messages = localeMessages[locale]; + + return new ImageResponse( + ( +
+
+ Kairas + {messages.Home.hero.kicker} +
+
+
+ {messages.Home.hero.words.join(" ")} +
+
+ {messages.Meta.description} +
+
+
+ Strategy / Design / Development + kairas.io +
+
+ ), + size, + ); +} diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx index 54ada76..f09402d 100644 --- a/src/app/[locale]/page.tsx +++ b/src/app/[locale]/page.tsx @@ -33,6 +33,56 @@ export default function Home() { const work = t.raw("work.items") as WorkItem[]; const processSteps = t.raw("process.steps") as ProcessStep[]; const notes = t.raw("notes.items") as NoteItem[]; + const siteUrl = "https://kairas.io"; + const pageUrl = `${siteUrl}/${locale}`; + const structuredData = [ + { + "@context": "https://schema.org", + "@type": "ProfessionalService", + "@id": `${siteUrl}/#organization`, + name: "Kairas", + url: siteUrl, + email: "hello@kairas.io", + image: `${pageUrl}/opengraph-image`, + description: t("hero.copy"), + areaServed: "Worldwide", + serviceType: services.map((service) => service.title), + knowsAbout: [ + "Web design", + "Brand websites", + "Design systems", + "Frontend development", + "Digital product design", + ], + sameAs: [siteUrl], + }, + { + "@context": "https://schema.org", + "@type": "WebSite", + "@id": `${siteUrl}/#website`, + name: "Kairas", + url: siteUrl, + inLanguage: locale, + publisher: { + "@id": `${siteUrl}/#organization`, + }, + }, + { + "@context": "https://schema.org", + "@type": "WebPage", + "@id": `${pageUrl}#webpage`, + url: pageUrl, + name: "Kairas", + description: t("hero.copy"), + inLanguage: locale, + isPartOf: { + "@id": `${siteUrl}/#website`, + }, + about: { + "@id": `${siteUrl}/#organization`, + }, + }, + ]; useEffect(() => { const root = rootRef.current; @@ -211,6 +261,10 @@ export default function Home() { ref={rootRef} className="relative min-h-screen bg-[var(--beige)] text-[var(--navy)]" > +