feat(seo): fix titles, canonicals, share tags and soft 404s #1

Merged
mohammad merged 1 commits from seo-batch into main 2026-10-14 11:15:31 +00:00
19 changed files with 261 additions and 73 deletions
Showing only changes of commit 8382501598 - Show all commits
+5 -3
View File
@@ -7,12 +7,14 @@ import { WhyUs } from '@/components/robotics/WhyUs';
import { FloatingTechPanel } from '@/components/robotics/FloatingTechPanel'; import { FloatingTechPanel } from '@/components/robotics/FloatingTechPanel';
import { CompanyStory } from '@/components/robotics/CompanyStory'; import { CompanyStory } from '@/components/robotics/CompanyStory';
import { FounderSection } from '@/components/robotics/FounderSection'; import { FounderSection } from '@/components/robotics/FounderSection';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'About YS Lootah Robotics UAE Destination for Unitree & Pudu Robotics', title: 'About Us · UAE Destination for Unitree & Pudu Robotics',
description: description:
'YS Lootah Robotics is part of the Yousuf Saeed Lootah Investment Group a trusted UAE robotics partner delivering AI, automation, and intelligent robotics across Dubai and the UAE.', 'YS Lootah Robotics is part of the Yousuf Saeed Lootah Investment Group a trusted UAE robotics partner delivering AI, automation, and intelligent robotics across Dubai and the UAE.',
}; path: '/about/',
});
export default function AboutPage() { export default function AboutPage() {
return ( return (
+10 -11
View File
@@ -10,9 +10,14 @@ import { FooterAndContact } from '@/components/FooterAndContact';
import { MotionSection } from '@/components/ui/MotionSection'; import { MotionSection } from '@/components/ui/MotionSection';
import { CTAButton } from '@/components/ui/CTAButton'; import { CTAButton } from '@/components/ui/CTAButton';
import { ACCESSORIES, GROUP_LABELS, getAccessoryBySlug, type Accessory } from '@/data/accessories'; import { ACCESSORIES, GROUP_LABELS, getAccessoryBySlug, type Accessory } from '@/data/accessories';
import { displayName, pageMetadata } from '@/lib/seo';
type Params = { slug: string }; type Params = { slug: string };
// Only the slugs below exist. Anything else is a real 404 from the router, not a
// page that renders "not found" in the browser with status 200.
export const dynamicParams = false;
export function generateStaticParams() { export function generateStaticParams() {
return ACCESSORIES.map((a) => ({ slug: a.slug })); return ACCESSORIES.map((a) => ({ slug: a.slug }));
} }
@@ -20,18 +25,12 @@ export function generateStaticParams() {
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> { export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
const { slug } = await params; const { slug } = await params;
const a = getAccessoryBySlug(slug); const a = getAccessoryBySlug(slug);
if (!a) return { title: 'Accessory not found · YS Lootah Robotics' }; if (!a) return { title: 'Accessory not found' };
return { return pageMetadata({
title: `${a.brandLabel} ${a.name} · Available in UAE | YS Lootah Robotics`, title: `${displayName(a.brandLabel, a.name)} · Available in UAE`,
description: `${a.tagline} ${a.description} Available through YS Lootah Robotics in Dubai.`, description: `${a.tagline} ${a.description} Available through YS Lootah Robotics in Dubai.`,
alternates: { canonical: `/accessories/${a.slug}/` }, path: `/accessories/${a.slug}`,
openGraph: { });
title: `${a.brandLabel} ${a.name}`,
description: a.tagline,
url: `/accessories/${a.slug}/`,
type: 'website',
},
};
} }
export default async function AccessoryDetailPage({ params }: { params: Promise<Params> }) { export default async function AccessoryDetailPage({ params }: { params: Promise<Params> }) {
+5 -4
View File
@@ -5,13 +5,14 @@ import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { AccessoriesShowcase } from '@/components/robotics/AccessoriesShowcase'; import { AccessoriesShowcase } from '@/components/robotics/AccessoriesShowcase';
import { ACCESSORIES } from '@/data/accessories'; import { ACCESSORIES } from '@/data/accessories';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Robotics Accessories · Dexterous Hands, Arms, Sensors, IoT | YS Lootah Robotics', title: 'Robotics Accessories · Dexterous Hands, Arms, Sensors, IoT',
description: description:
'Browse Unitree dexterous hands, robotic arms, perception sensors, and Pudu dispatch, docking, modular attachments, and building IoT modules — available in the UAE through YS Lootah Robotics.', 'Browse Unitree dexterous hands, robotic arms, perception sensors, and Pudu dispatch, docking, modular attachments, and building IoT modules — available in the UAE through YS Lootah Robotics.',
alternates: { canonical: '/accessories/' }, path: '/accessories/',
}; });
export default function AccessoriesIndexPage() { export default function AccessoriesIndexPage() {
return ( return (
+5 -3
View File
@@ -2,12 +2,14 @@ import type { Metadata } from 'next';
import { Navbar } from '@/components/Navbar'; import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { InquiryForm } from '@/components/robotics/InquiryForm'; import { InquiryForm } from '@/components/robotics/InquiryForm';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Book a Robotics Demo in Dubai YS Lootah Robotics', title: 'Book a Robotics Demo in Dubai',
description: description:
'Book a live robot demo at our Dubai showroom or at your venue. See selected Unitree and Pudu Robotics solutions in action, available in the UAE through YS Lootah Robotics.', 'Book a live robot demo at our Dubai showroom or at your venue. See selected Unitree and Pudu Robotics solutions in action, available in the UAE through YS Lootah Robotics.',
}; path: '/book-demo/',
});
export default function BookDemoPage() { export default function BookDemoPage() {
return ( return (
+5 -3
View File
@@ -5,12 +5,14 @@ import { BRANDS, ROBOTS, type RobotBrand } from '@/data/robots';
import { RobotProductCard } from '@/components/robotics/RobotProductCard'; import { RobotProductCard } from '@/components/robotics/RobotProductCard';
import { MotionSection } from '@/components/ui/MotionSection'; import { MotionSection } from '@/components/ui/MotionSection';
import { DemoCTA } from '@/components/robotics/DemoCTA'; import { DemoCTA } from '@/components/robotics/DemoCTA';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Robotics Brands Unitree & Pudu | UAE Access via YS Lootah Robotics', title: 'Robotics Brands in the UAE · Unitree & Pudu',
description: description:
'Selected Unitree and Pudu Robotics solutions available in the UAE through YS Lootah Robotics. Humanoid, quadruped, service, delivery, and cleaning robots.', 'Selected Unitree and Pudu Robotics solutions available in the UAE through YS Lootah Robotics. Humanoid, quadruped, service, delivery, and cleaning robots.',
}; path: '/brands/',
});
const ORDER: RobotBrand[] = ['unitree', 'pudu']; const ORDER: RobotBrand[] = ['unitree', 'pudu'];
+5 -10
View File
@@ -23,22 +23,17 @@ import {
} from '@/data/bu-sunaidah'; } from '@/data/bu-sunaidah';
import { BU_SUNAIDAH_PRESS } from '@/data/bu-sunaidah-press'; import { BU_SUNAIDAH_PRESS } from '@/data/bu-sunaidah-press';
import { getLatestBuSunaidahPosts } from '@/lib/bu-sunaidah-fetch'; import { getLatestBuSunaidahPosts } from '@/lib/bu-sunaidah-fetch';
import { pageMetadata } from '@/lib/seo';
// Re-fetch latest Instagram posts every 30 minutes on the server. // Re-fetch latest Instagram posts every 30 minutes on the server.
export const revalidate = 1800; export const revalidate = 1800;
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Bu Sunaidah · Emirati Robotics Persona | YS Lootah Robotics', title: 'Bu Sunaidah · Emirati Robotics Persona',
description: description:
'Meet Bu Sunaidah — the Emirati culture-inspired robotics character from YS Lootah Robotics. Media activations, exhibitions, and community storytelling across the UAE.', 'Meet Bu Sunaidah — the Emirati culture-inspired robotics character from YS Lootah Robotics. Media activations, exhibitions, and community storytelling across the UAE.',
alternates: { canonical: '/bu-sunaidah/' }, path: '/bu-sunaidah/',
openGraph: { });
title: 'Bu Sunaidah · Emirati Robotics Persona',
description: 'Emirati culture-inspired robotics character — UAE media, exhibitions, community.',
url: '/bu-sunaidah/',
type: 'website',
},
};
const PILLARS = [ const PILLARS = [
{ {
+14
View File
@@ -0,0 +1,14 @@
import type { Metadata } from 'next';
import { pageMetadata } from '@/lib/seo';
// The page itself is a client component and cannot export metadata.
export const metadata: Metadata = pageMetadata({
title: 'Unitree G1 3D Configurator',
description:
'Build your Unitree G1 in 3D. Choose persona, attire, colours and accessories, preview it live, then request a UAE quotation from YS Lootah Robotics.',
path: '/configure/',
});
export default function ConfigureLayout({ children }: { children: React.ReactNode }) {
return children;
}
+5 -3
View File
@@ -2,12 +2,14 @@ import type { Metadata } from 'next';
import { Navbar } from '@/components/Navbar'; import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { InquiryForm } from '@/components/robotics/InquiryForm'; import { InquiryForm } from '@/components/robotics/InquiryForm';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Contact YS Lootah Robotics Dubai Robotics Sales & Demo Inquiries', title: 'Contact · Dubai Robotics Sales & Demo Inquiries',
description: description:
'Dubai robotics sales, support, and demo inquiries. Contact YS Lootah Robotics, the UAE destination for selected Unitree and Pudu Robotics solutions, by phone, email, or WhatsApp.', 'Dubai robotics sales, support, and demo inquiries. Contact YS Lootah Robotics, the UAE destination for selected Unitree and Pudu Robotics solutions, by phone, email, or WhatsApp.',
}; path: '/contact/',
});
export default function ContactPage() { export default function ContactPage() {
return ( return (
+10 -6
View File
@@ -26,6 +26,7 @@ import { MotionSection } from '@/components/ui/MotionSection';
import { CTAButton } from '@/components/ui/CTAButton'; import { CTAButton } from '@/components/ui/CTAButton';
import { INDUSTRIES, getIndustryBySlug } from '@/data/industries'; import { INDUSTRIES, getIndustryBySlug } from '@/data/industries';
import { INDUSTRY_PAGES, type IndustrySolution } from '@/data/industry-pages'; import { INDUSTRY_PAGES, type IndustrySolution } from '@/data/industry-pages';
import { pageMetadata } from '@/lib/seo';
type Params = { slug: string }; type Params = { slug: string };
@@ -44,6 +45,10 @@ const ICONS: Record<IndustrySolution['iconKey'], React.ComponentType<{ size?: nu
tray: UtensilsCrossed, tray: UtensilsCrossed,
}; };
// Only the slugs below exist. Anything else is a real 404 from the router, not a
// page that renders "not found" in the browser with status 200.
export const dynamicParams = false;
export function generateStaticParams() { export function generateStaticParams() {
return INDUSTRIES.map((i) => ({ slug: i.slug })); return INDUSTRIES.map((i) => ({ slug: i.slug }));
} }
@@ -54,14 +59,13 @@ export async function generateMetadata({ params }: { params: Promise<Params> }):
if (!industry) return {}; if (!industry) return {};
const content = INDUSTRY_PAGES[industry.id]; const content = INDUSTRY_PAGES[industry.id];
if (!content) return {}; if (!content) return {};
const url = `/industries/${industry.slug}/`; return pageMetadata({
return {
title: content.seoTitle, title: content.seoTitle,
description: content.seoDescription, description: content.seoDescription,
alternates: { canonical: url }, path: `/industries/${industry.slug}`,
openGraph: { title: content.seoTitle, description: content.seoDescription, url, type: 'website' }, image: industry.image,
twitter: { card: 'summary_large_image', title: content.seoTitle, description: content.seoDescription }, imageAlt: industry.imageAlt,
}; });
} }
const TRUST_DEFAULT = [ const TRUST_DEFAULT = [
+6 -4
View File
@@ -9,12 +9,14 @@ import { FooterAndContact } from '@/components/FooterAndContact';
import { IndustryUseCases } from '@/components/robotics/IndustryUseCases'; import { IndustryUseCases } from '@/components/robotics/IndustryUseCases';
import { DemoCTA } from '@/components/robotics/DemoCTA'; import { DemoCTA } from '@/components/robotics/DemoCTA';
import { MotionSection } from '@/components/ui/MotionSection'; import { MotionSection } from '@/components/ui/MotionSection';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Robotics for UAE Industries YS Lootah Robotics Dubai', title: 'Robotics for UAE Industries',
description: description:
'Robotics solutions for hospitality, restaurants, hotels, healthcare, education, security, warehouses, smart buildings, and government across the UAE.', 'Robotics solutions for hospitality, restaurants, hotels, healthcare, education, security, and warehouses across the UAE.',
}; path: '/industries/',
});
const HERO_COLLAGE: { src: string; alt: string }[] = [ const HERO_COLLAGE: { src: string; alt: string }[] = [
{ src: '/industries/cards/hospitality.webp', alt: 'Service robot in a luxury hospitality lobby' }, { src: '/industries/cards/hospitality.webp', alt: 'Service robot in a luxury hospitality lobby' },
+39
View File
@@ -0,0 +1,39 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact';
// Next adds the noindex tag to not-found responses itself.
export const metadata: Metadata = {
title: 'Page not found',
};
export default function NotFound() {
return (
<>
<Navbar />
<main style={{ paddingTop: 'clamp(8rem, 14vw, 11rem)', paddingBottom: 'clamp(5rem, 10vw, 8rem)' }}>
<div
className="container-wide"
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1.5rem', textAlign: 'center' }}
>
<span className="eyebrow">Error 404</span>
<h1 style={{ fontSize: 'clamp(2.2rem, 6vw, 4rem)', fontWeight: 600, letterSpacing: '-0.03em', lineHeight: 1.05, color: '#f5f5f5', maxWidth: '16ch' }}>
This page could not be found.
</h1>
<p style={{ color: '#a8a8a8', maxWidth: 520, lineHeight: 1.7 }}>
The address may have changed or the product may no longer be listed. Browse the catalog or
get in touch and we will point you to the right robot.
</p>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: '0.75rem 1.5rem', marginTop: '0.5rem' }}>
<Link href="/robots/" style={{ color: '#f5f5f5' }}>Robots</Link>
<Link href="/accessories/" style={{ color: '#f5f5f5' }}>Accessories</Link>
<Link href="/industries/" style={{ color: '#f5f5f5' }}>Industries</Link>
<Link href="/contact/" style={{ color: '#f5f5f5' }}>Contact</Link>
</div>
</div>
</main>
<FooterAndContact />
</>
);
}
+9
View File
@@ -1,6 +1,15 @@
import type { Metadata } from 'next';
import React from 'react'; import React from 'react';
import { Navbar } from '@/components/Navbar'; import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = pageMetadata({
title: 'Privacy Policy',
description:
'How YS Lootah Robotics collects, uses and protects personal information on this website and in enquiries, demo bookings and orders.',
path: '/privacy-policy/',
});
export default function PrivacyPolicyPage() { export default function PrivacyPolicyPage() {
return ( return (
+17 -14
View File
@@ -12,10 +12,14 @@ import { ProductGallery } from '@/components/robotics/ProductGallery';
import { MotionSection } from '@/components/ui/MotionSection'; import { MotionSection } from '@/components/ui/MotionSection';
import { ROBOTS, getRobotBySlug, BRANDS, CATEGORY_LABELS } from '@/data/robots'; import { ROBOTS, getRobotBySlug, BRANDS, CATEGORY_LABELS } from '@/data/robots';
import JsonLd from '@/components/JsonLd'; import JsonLd from '@/components/JsonLd';
import { canonical, abs, productJsonLd, breadcrumbJsonLd } from '@/lib/seo'; import { displayName, pageMetadata, productJsonLd, breadcrumbJsonLd } from '@/lib/seo';
type Params = { slug: string }; type Params = { slug: string };
// Only the slugs below exist. Anything else is a real 404 from the router, not a
// page that renders "not found" in the browser with status 200.
export const dynamicParams = false;
export function generateStaticParams() { export function generateStaticParams() {
return ROBOTS.map((r) => ({ slug: r.slug })); return ROBOTS.map((r) => ({ slug: r.slug }));
} }
@@ -23,18 +27,17 @@ export function generateStaticParams() {
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> { export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
const { slug } = await params; const { slug } = await params;
const robot = getRobotBySlug(slug); const robot = getRobotBySlug(slug);
if (!robot) return { title: 'Robot not found YS Lootah Robotics' }; if (!robot) return { title: 'Robot not found' };
const url = canonical(`/robots/${robot.slug}`); const name = displayName(robot.brandLabel, robot.name);
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 { return {
title, ...pageMetadata({
description, title: `${name} Available in Dubai`,
description: `${robot.shortDescription} Available through YS Lootah Robotics in Dubai request a price or book a live demo.`,
path: `/robots/${robot.slug}`,
image: robot.image,
imageAlt: name,
}),
keywords: [robot.brandLabel, robot.name, 'Dubai', 'UAE', CATEGORY_LABELS[robot.category], 'robotics'], 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] },
}; };
} }
@@ -47,7 +50,7 @@ export default async function RobotDetailPage({ params }: { params: Promise<Para
const related = ROBOTS.filter((r) => r.id !== robot.id && (r.brand === robot.brand || r.category === robot.category)).slice(0, 3); const related = ROBOTS.filter((r) => r.id !== robot.id && (r.brand === robot.brand || r.category === robot.category)).slice(0, 3);
const productLd = productJsonLd({ const productLd = productJsonLd({
name: `${robot.brandLabel} ${robot.name}`, name: displayName(robot.brandLabel, robot.name),
description: robot.shortDescription, description: robot.shortDescription,
image: robot.image, image: robot.image,
brandName: brand.name, brandName: brand.name,
@@ -85,7 +88,7 @@ export default async function RobotDetailPage({ params }: { params: Promise<Para
> >
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<ProductGallery <ProductGallery
images={[{ src: robot.image, alt: `${robot.brandLabel} ${robot.name}` }]} images={[{ src: robot.image, alt: displayName(robot.brandLabel, robot.name) }]}
accent={robot.accent} accent={robot.accent}
/> />
{robot.imageType === 'placeholder' && ( {robot.imageType === 'placeholder' && (
@@ -184,7 +187,7 @@ export default async function RobotDetailPage({ params }: { params: Promise<Para
Tell us about your venue, timeline, and use case. We will respond with availability, configuration options, and pricing for the UAE. Tell us about your venue, timeline, and use case. We will respond with availability, configuration options, and pricing for the UAE.
</p> </p>
</div> </div>
<InquiryForm defaultRobot={`${robot.brandLabel} ${robot.name}`} defaultCategory={robot.category} intent="quotation" /> <InquiryForm defaultRobot={displayName(robot.brandLabel, robot.name)} defaultCategory={robot.category} intent="quotation" />
</div> </div>
<div className="card" style={{ padding: 'clamp(1.5rem, 4vw, 2.25rem)', display: 'flex', flexDirection: 'column', gap: '1rem' }}> <div className="card" style={{ padding: 'clamp(1.5rem, 4vw, 2.25rem)', display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+5 -4
View File
@@ -9,13 +9,14 @@ import { RoboticsSplineShowcase } from '@/components/sections/robotics-spline-sh
import { CTAButton } from '@/components/ui/CTAButton'; import { CTAButton } from '@/components/ui/CTAButton';
import { ROBOTS } from '@/data/robots'; import { ROBOTS } from '@/data/robots';
import { ACCESSORIES } from '@/data/accessories'; import { ACCESSORIES } from '@/data/accessories';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Robots Catalog · Unitree & Pudu in UAE | YS Lootah Robotics', title: 'Robots Catalog · Unitree & Pudu in UAE',
description: description:
'Explore selected humanoid, quadruped, industrial, service, delivery, hospitality, and cleaning robots — plus dexterous hands, arms, sensors, and accessories — from Unitree and Pudu available in the UAE through YS Lootah Robotics.', 'Explore selected humanoid, quadruped, industrial, service, delivery, hospitality, and cleaning robots — plus dexterous hands, arms, sensors, and accessories — from Unitree and Pudu available in the UAE through YS Lootah Robotics.',
alternates: { canonical: '/robots/' }, path: '/robots/',
}; });
const unitreeCount = ROBOTS.filter((r) => r.brand === 'unitree').length; const unitreeCount = ROBOTS.filter((r) => r.brand === 'unitree').length;
const puduCount = ROBOTS.filter((r) => r.brand === 'pudu').length; const puduCount = ROBOTS.filter((r) => r.brand === 'pudu').length;
+9
View File
@@ -1,6 +1,15 @@
import type { Metadata } from 'next';
import React from 'react'; import React from 'react';
import { Navbar } from '@/components/Navbar'; import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = pageMetadata({
title: 'Terms of Service',
description:
'The terms that apply to using the YS Lootah Robotics website, requesting quotations, booking demos and ordering robots in the UAE.',
path: '/terms-of-service/',
});
export default function TermsOfServicePage() { export default function TermsOfServicePage() {
return ( return (
+6
View File
@@ -119,6 +119,12 @@ export const INDUSTRIES: Industry[] = [
image: '/industries/cards/warehouses-logistics.webp', image: '/industries/cards/warehouses-logistics.webp',
imageAlt: 'Industrial robot for UAE warehouses and logistics operations', imageAlt: 'Industrial robot for UAE warehouses and logistics operations',
}, },
];
// Sectors with no detail page content yet (no entry in INDUSTRY_PAGES). They are
// kept out of INDUSTRIES so no card, sitemap entry or route points at an empty
// page. Move an entry back up once its content is written.
export const UPCOMING_INDUSTRIES: Industry[] = [
{ {
id: 'events', id: 'events',
slug: 'events', slug: 'events',
+9 -8
View File
@@ -54,6 +54,7 @@ export type IndustryPageContent = {
suggestedRobots: SuggestedRobot[]; suggestedRobots: SuggestedRobot[];
ctaTitle?: string; ctaTitle?: string;
ctaBody?: string; ctaBody?: string;
/** Without the brand: the layout title template appends it. */
seoTitle: string; seoTitle: string;
seoDescription: string; seoDescription: string;
}; };
@@ -141,7 +142,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Humanoid', 'Concierge', 'Premium'], chips: ['Humanoid', 'Concierge', 'Premium'],
}, },
], ],
seoTitle: 'Hospitality Robots in UAE | YS Lootah Robotics', seoTitle: 'Hospitality Robots in UAE',
seoDescription: seoDescription:
'Service, delivery, cleaning, and concierge robots for hotels, lounges, and hospitality venues across the UAE. Pudu and Unitree solutions available in the UAE through YS Lootah Robotics.', 'Service, delivery, cleaning, and concierge robots for hotels, lounges, and hospitality venues across the UAE. Pudu and Unitree solutions available in the UAE through YS Lootah Robotics.',
}, },
@@ -223,7 +224,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Compact', 'Auto-doors', 'Multi-zone'], chips: ['Compact', 'Auto-doors', 'Multi-zone'],
}, },
], ],
seoTitle: 'Restaurant & Café Robots in UAE | YS Lootah Robotics', seoTitle: 'Restaurant & Café Robots in UAE',
seoDescription: seoDescription:
'Delivery, host, and tray-runner robots for UAE restaurants, cafés, and F&B venues. Faster service, lower staff fatigue, premium positioning.', 'Delivery, host, and tray-runner robots for UAE restaurants, cafés, and F&B venues. Faster service, lower staff fatigue, premium positioning.',
}, },
@@ -305,7 +306,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Humanoid', 'Concierge', 'Activation'], chips: ['Humanoid', 'Concierge', 'Activation'],
}, },
], ],
seoTitle: 'Hotel & Resort Robots in UAE | YS Lootah Robotics', seoTitle: 'Hotel & Resort Robots in UAE',
seoDescription: seoDescription:
'Multi-floor delivery, cleaning, and humanoid concierge robots for hotels and resorts across the UAE. Local Dubai support and deployment.', 'Multi-floor delivery, cleaning, and humanoid concierge robots for hotels and resorts across the UAE. Local Dubai support and deployment.',
}, },
@@ -393,7 +394,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Humanoid', 'Activation', 'PR'], chips: ['Humanoid', 'Activation', 'PR'],
}, },
], ],
seoTitle: 'Shopping Mall Robots in UAE | YS Lootah Robotics', seoTitle: 'Shopping Mall Robots in UAE',
seoDescription: seoDescription:
'Cleaning, service, activation, and patrol robots for UAE shopping malls and retail flagships. Pudu and Unitree, deployed and supported locally.', 'Cleaning, service, activation, and patrol robots for UAE shopping malls and retail flagships. Pudu and Unitree, deployed and supported locally.',
}, },
@@ -475,7 +476,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Greeter', 'Wayfinding', 'Ad screen'], chips: ['Greeter', 'Wayfinding', 'Ad screen'],
}, },
], ],
seoTitle: 'Healthcare Robots in UAE | YS Lootah Robotics', seoTitle: 'Healthcare Robots in UAE',
seoDescription: seoDescription:
'Autonomous delivery, cleaning, and visitor-guidance robots for UAE clinics, hospitals, and healthcare networks. Local support from Dubai.', 'Autonomous delivery, cleaning, and visitor-guidance robots for UAE clinics, hospitals, and healthcare networks. Local support from Dubai.',
}, },
@@ -584,7 +585,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
ctaTitle: 'Build a future-ready robotics program.', ctaTitle: 'Build a future-ready robotics program.',
ctaBody: ctaBody:
'Tell us about your school, university, or innovation lab. Our Dubai team will recommend the right robotics setup and arrange a demo or quotation.', 'Tell us about your school, university, or innovation lab. Our Dubai team will recommend the right robotics setup and arrange a demo or quotation.',
seoTitle: 'Education & STEM Robots in UAE | YS Lootah Robotics', seoTitle: 'Education & STEM Robots in UAE',
seoDescription: seoDescription:
'Humanoid and quadruped robots for UAE schools, universities, and innovation labs. Hands-on AI and robotics education with local Dubai support.', 'Humanoid and quadruped robots for UAE schools, universities, and innovation labs. Hands-on AI and robotics education with local Dubai support.',
}, },
@@ -700,7 +701,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Thermal', 'LiDAR', 'AI vision'], chips: ['Thermal', 'LiDAR', 'AI vision'],
}, },
], ],
seoTitle: 'Security & Surveillance Robots in UAE | YS Lootah Robotics', seoTitle: 'Security & Surveillance Robots in UAE',
seoDescription: seoDescription:
'Quadruped patrol, inspection, and perimeter robots for UAE facilities, compounds, and critical sites. Unitree platforms with local support.', 'Quadruped patrol, inspection, and perimeter robots for UAE facilities, compounds, and critical sites. Unitree platforms with local support.',
}, },
@@ -792,7 +793,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Thermal', 'LiDAR', 'AI'], chips: ['Thermal', 'LiDAR', 'AI'],
}, },
], ],
seoTitle: 'Warehouse & Logistics Robots in UAE | YS Lootah Robotics', seoTitle: 'Warehouse & Logistics Robots in UAE',
seoDescription: seoDescription:
'Quadruped inspection, autonomous transport, and monitoring robots for UAE warehouses, logistics, and industrial sites. Local Dubai deployment.', 'Quadruped inspection, autonomous transport, and monitoring robots for UAE warehouses, logistics, and industrial sites. Local Dubai deployment.',
}, },
+58
View File
@@ -0,0 +1,58 @@
import { describe, expect, it } from 'vitest';
import { ROBOTS } from '@/data/robots';
import { ACCESSORIES } from '@/data/accessories';
import { INDUSTRIES } from '@/data/industries';
import { INDUSTRY_PAGES } from '@/data/industry-pages';
import { canonical, displayName, pageMetadata, siteName } from './seo';
describe('displayName', () => {
it('joins brand and name', () => {
expect(displayName('Unitree', 'G1 Humanoid')).toBe('Unitree G1 Humanoid');
});
it('does not repeat a brand the name already starts with', () => {
expect(displayName('Pudu', 'PUDU CC1')).toBe('PUDU CC1');
expect(displayName('Pudu', 'PuduBot 2')).toBe('PuduBot 2');
});
it('never yields a doubled brand for any catalog item', () => {
for (const item of [...ROBOTS, ...ACCESSORIES]) {
const name = displayName(item.brandLabel, item.name).toLowerCase();
const brand = item.brandLabel.toLowerCase();
expect(name.startsWith(`${brand} ${brand}`), name).toBe(false);
}
});
});
describe('pageMetadata', () => {
const meta = pageMetadata({ title: 'Robots Catalog', description: 'd', path: '/robots' });
it('leaves the brand out of the title, because the layout template appends it', () => {
expect(meta.title).not.toContain(siteName);
});
it('adds the brand once to share titles, which get no template', () => {
expect(meta.openGraph.title).toBe(`Robots Catalog | ${siteName}`);
expect(meta.twitter.title).toBe(meta.openGraph.title);
});
it('points canonical and og:url at the page itself, with a trailing slash', () => {
expect(meta.alternates.canonical).toBe(canonical('/robots/'));
expect(meta.openGraph.url).toBe(meta.alternates.canonical);
expect(meta.alternates.canonical.endsWith('/robots/')).toBe(true);
});
});
describe('industries', () => {
it('lists only sectors that have a detail page', () => {
for (const industry of INDUSTRIES) {
expect(INDUSTRY_PAGES[industry.id], industry.slug).toBeDefined();
}
});
it('keeps the brand out of industry titles', () => {
for (const page of Object.values(INDUSTRY_PAGES)) {
expect(page.seoTitle).not.toContain(siteName);
}
});
});
+39
View File
@@ -15,6 +15,45 @@ export const siteDescription =
'YS Lootah Robotics is the UAE sales destination for selected Unitree, AgiBot and Pudu robotics. Explore, configure and book a demo of humanoid, quadruped, delivery and cleaning robots across Dubai and the UAE.'; 'YS Lootah Robotics is the UAE sales destination for selected Unitree, AgiBot and Pudu robotics. Explore, configure and book a demo of humanoid, quadruped, delivery and cleaning robots across Dubai and the UAE.';
export const OG_IMAGE = abs('/og.jpg'); export const OG_IMAGE = abs('/og.jpg');
// "Pudu" + "PUDU CC1" would read "Pudu PUDU CC1": drop the label when the name already starts with it.
export const displayName = (brandLabel: string, name: string) =>
name.toLowerCase().startsWith(brandLabel.toLowerCase()) ? name : `${brandLabel} ${name}`;
type PageMeta = {
/** Page title without the brand. The root layout template appends " | YS Lootah Robotics". */
title: string;
description: string;
/** Path on this site: '/robots/', '/about/'. */
path: string;
/** Share image path or URL. Defaults to the site image. */
image?: string;
imageAlt?: string;
};
// One place for title, canonical and share tags, so no page is left with the
// homepage's og:url or without a canonical. The title template does not apply
// to openGraph or twitter titles, so the brand is added to those here.
export function pageMetadata({ title, description, path, image, imageAlt }: PageMeta) {
const url = canonical(path);
const fullTitle = `${title} | ${siteName}`;
const img = image ? (image.startsWith('http') ? image : abs(image)) : OG_IMAGE;
return {
title,
description,
alternates: { canonical: url },
openGraph: {
type: 'website' as const,
url,
title: fullTitle,
description,
siteName,
locale: 'en_AE',
images: [{ url: img, alt: imageAlt ?? siteName }],
},
twitter: { card: 'summary_large_image' as const, title: fullTitle, description, images: [img] },
};
}
export const business = { export const business = {
legalName: 'YS Lootah Robotics', legalName: 'YS Lootah Robotics',
parent: 'Yousuf Saeed Lootah Investment Group', parent: 'Yousuf Saeed Lootah Investment Group',