feat(seo): robots.txt, richer sitemap, llms.txt, manifest, OG image, JSON-LD
- Add app/robots.ts (allow crawl, disallow admin/api, sitemap URL) - Expand sitemap to industries/[slug], accessories/[slug] and more static routes - Add public/llms.txt (AI summary), site.webmanifest, 1200x630 og.jpg - Layout: default OG/Twitter image, manifest, robots meta, Organization + WebSite JSON-LD - Robot pages: canonical, OG image, Product + BreadcrumbList JSON-LD; home canonical Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
873026a4a9
commit
60d50e15e4
@@ -1,6 +1,8 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { ErrorBoundary } from "@/components/ErrorBoundary";
|
||||
import { I18nProvider } from "@/components/I18nProvider";
|
||||
import JsonLd from "@/components/JsonLd";
|
||||
import { OG_IMAGE, siteName, organizationJsonLd, websiteJsonLd } from "@/lib/seo";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -28,17 +30,32 @@ export const metadata: Metadata = {
|
||||
],
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
url: '/',
|
||||
title: 'YS Lootah Robotics UAE Destination for Unitree & Pudu Robotics',
|
||||
description:
|
||||
'Advanced robotics. UAE access. Selected Unitree and Pudu solutions available through YS Lootah Robotics in Dubai.',
|
||||
locale: 'en_AE',
|
||||
siteName: 'YS Lootah Robotics',
|
||||
images: [{ url: OG_IMAGE, width: 1200, height: 630, alt: siteName }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'YS Lootah Robotics UAE Destination for Unitree & Pudu Robotics',
|
||||
description:
|
||||
'The UAE’s dedicated destination for Unitree and Pudu Robotics sales, demo inquiries, and business deployment.',
|
||||
images: [OG_IMAGE],
|
||||
},
|
||||
manifest: '/site.webmanifest',
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
'max-image-preview': 'large',
|
||||
'max-snippet': -1,
|
||||
'max-video-preview': -1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -63,6 +80,7 @@ export default function RootLayout({
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body suppressHydrationWarning>
|
||||
<JsonLd data={[organizationJsonLd, websiteJsonLd]} />
|
||||
<ErrorBoundary>
|
||||
<I18nProvider>
|
||||
{children}
|
||||
|
||||
@@ -35,6 +35,7 @@ export const metadata: Metadata = {
|
||||
'robotics configuration UAE',
|
||||
'robot sales and demo UAE',
|
||||
],
|
||||
alternates: { canonical: '/' },
|
||||
};
|
||||
|
||||
export default function HomePage() {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
import { SITE_URL } from '@/lib/seo';
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: [
|
||||
{
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
disallow: ['/admin', '/admin/', '/api/'],
|
||||
},
|
||||
],
|
||||
sitemap: `${SITE_URL}/sitemap.xml`,
|
||||
host: SITE_URL,
|
||||
};
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import { ProductSpecTable } from '@/components/robotics/ProductSpecTable';
|
||||
import { ProductGallery } from '@/components/robotics/ProductGallery';
|
||||
import { MotionSection } from '@/components/ui/MotionSection';
|
||||
import { ROBOTS, getRobotBySlug, BRANDS, CATEGORY_LABELS } from '@/data/robots';
|
||||
import JsonLd from '@/components/JsonLd';
|
||||
import { canonical, abs, productJsonLd, breadcrumbJsonLd } from '@/lib/seo';
|
||||
|
||||
type Params = { slug: string };
|
||||
|
||||
@@ -22,10 +24,17 @@ export async function generateMetadata({ params }: { params: Promise<Params> }):
|
||||
const { slug } = await params;
|
||||
const robot = getRobotBySlug(slug);
|
||||
if (!robot) return { title: 'Robot not found YS Lootah Robotics' };
|
||||
const url = canonical(`/robots/${robot.slug}`);
|
||||
const img = abs(robot.image);
|
||||
const title = `${robot.brandLabel} ${robot.name} Available in Dubai | YS Lootah Robotics`;
|
||||
const description = `${robot.shortDescription} Available through YS Lootah Robotics in Dubai request a price or book a live demo.`;
|
||||
return {
|
||||
title: `${robot.brandLabel} ${robot.name} Available in Dubai | YS Lootah Robotics`,
|
||||
description: `${robot.shortDescription} Available through YS Lootah Robotics in Dubai request a price or book a live demo.`,
|
||||
title,
|
||||
description,
|
||||
keywords: [robot.brandLabel, robot.name, 'Dubai', 'UAE', CATEGORY_LABELS[robot.category], 'robotics'],
|
||||
alternates: { canonical: url },
|
||||
openGraph: { type: 'website', url, title, description, images: [{ url: img, alt: `${robot.brandLabel} ${robot.name}` }] },
|
||||
twitter: { card: 'summary_large_image', title, description, images: [img] },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,8 +46,23 @@ export default async function RobotDetailPage({ params }: { params: Promise<Para
|
||||
const brand = BRANDS[robot.brand];
|
||||
const related = ROBOTS.filter((r) => r.id !== robot.id && (r.brand === robot.brand || r.category === robot.category)).slice(0, 3);
|
||||
|
||||
const productLd = productJsonLd({
|
||||
name: `${robot.brandLabel} ${robot.name}`,
|
||||
description: robot.shortDescription,
|
||||
image: robot.image,
|
||||
brandName: brand.name,
|
||||
path: `/robots/${robot.slug}`,
|
||||
category: CATEGORY_LABELS[robot.category],
|
||||
});
|
||||
const crumbLd = breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Robots', path: '/robots' },
|
||||
{ name: robot.name, path: `/robots/${robot.slug}` },
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<JsonLd data={[productLd, crumbLd]} />
|
||||
<Navbar />
|
||||
|
||||
<main style={{ paddingTop: 'clamp(6rem, 10vw, 8rem)', paddingBottom: 'clamp(4rem, 8vw, 6rem)' }}>
|
||||
|
||||
+33
-14
@@ -1,29 +1,48 @@
|
||||
import { MetadataRoute } from 'next';
|
||||
import { ROBOTS } from '@/data/robots';
|
||||
|
||||
const baseUrl = 'https://yslootahrobotics.com';
|
||||
import { INDUSTRIES } from '@/data/industries';
|
||||
import { ACCESSORIES } from '@/data/accessories';
|
||||
import { SITE_URL } from '@/lib/seo';
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const now = new Date();
|
||||
const u = (path: string) => `${SITE_URL}${path}`;
|
||||
|
||||
const staticRoutes: MetadataRoute.Sitemap = [
|
||||
{ url: `${baseUrl}/`, lastModified: now, changeFrequency: 'weekly', priority: 1 },
|
||||
{ url: `${baseUrl}/robots/`, lastModified: now, changeFrequency: 'weekly', priority: 0.9 },
|
||||
{ url: `${baseUrl}/brands/`, lastModified: now, changeFrequency: 'weekly', priority: 0.8 },
|
||||
{ url: `${baseUrl}/industries/`, lastModified: now, changeFrequency: 'weekly', priority: 0.8 },
|
||||
{ url: `${baseUrl}/about/`, lastModified: now, changeFrequency: 'monthly', priority: 0.6 },
|
||||
{ url: `${baseUrl}/contact/`, lastModified: now, changeFrequency: 'monthly', priority: 0.7 },
|
||||
{ url: `${baseUrl}/book-demo/`, lastModified: now, changeFrequency: 'monthly', priority: 0.8 },
|
||||
{ url: `${baseUrl}/configure/`, lastModified: now, changeFrequency: 'monthly', priority: 0.8 },
|
||||
{ url: `${baseUrl}/privacy-policy/`, lastModified: now, changeFrequency: 'yearly', priority: 0.3 },
|
||||
{ url: `${baseUrl}/terms-of-service/`, lastModified: now, changeFrequency: 'yearly', priority: 0.3 },
|
||||
{ url: u('/'), lastModified: now, changeFrequency: 'weekly', priority: 1 },
|
||||
{ url: u('/robots/'), lastModified: now, changeFrequency: 'weekly', priority: 0.9 },
|
||||
{ url: u('/configure/'), lastModified: now, changeFrequency: 'weekly', priority: 0.9 },
|
||||
{ url: u('/brands/'), lastModified: now, changeFrequency: 'weekly', priority: 0.8 },
|
||||
{ url: u('/industries/'), lastModified: now, changeFrequency: 'weekly', priority: 0.8 },
|
||||
{ url: u('/accessories/'), lastModified: now, changeFrequency: 'weekly', priority: 0.7 },
|
||||
{ url: u('/book-demo/'), lastModified: now, changeFrequency: 'monthly', priority: 0.8 },
|
||||
{ url: u('/contact/'), lastModified: now, changeFrequency: 'monthly', priority: 0.7 },
|
||||
{ url: u('/about/'), lastModified: now, changeFrequency: 'monthly', priority: 0.6 },
|
||||
{ url: u('/bu-sunaidah/'), lastModified: now, changeFrequency: 'monthly', priority: 0.5 },
|
||||
{ url: u('/privacy-policy/'), lastModified: now, changeFrequency: 'yearly', priority: 0.3 },
|
||||
{ url: u('/terms-of-service/'), lastModified: now, changeFrequency: 'yearly', priority: 0.3 },
|
||||
];
|
||||
|
||||
const robotRoutes: MetadataRoute.Sitemap = ROBOTS.map((r) => ({
|
||||
url: `${baseUrl}/robots/${r.slug}/`,
|
||||
url: u(`/robots/${r.slug}/`),
|
||||
lastModified: now,
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.7,
|
||||
}));
|
||||
|
||||
return [...staticRoutes, ...robotRoutes];
|
||||
const industryRoutes: MetadataRoute.Sitemap = INDUSTRIES.map((i) => ({
|
||||
url: u(`/industries/${i.slug}/`),
|
||||
lastModified: now,
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.6,
|
||||
}));
|
||||
|
||||
const accessoryRoutes: MetadataRoute.Sitemap = ACCESSORIES.map((a) => ({
|
||||
url: u(`/accessories/${a.slug}/`),
|
||||
lastModified: now,
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.5,
|
||||
}));
|
||||
|
||||
return [...staticRoutes, ...robotRoutes, ...industryRoutes, ...accessoryRoutes];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user