- Add metadataBase and localized generateMetadata to all pages (EN/FR) - Add Open Graph and Twitter card defaults with branded OG image - Add canonical URLs and hreflang alternates on every page - Create robots.ts (allow all, block /admin/ and /api/) - Create sitemap.ts with all routes x 2 locales and hreflang - Add Organization, WebSite, and CreativeWork JSON-LD structured data - Add GA4 (G-LD348WF8EM) with Consent Mode v2 (default denied for EEA) - Add llms.txt for AI discoverability - Add production nginx config for letsbe.biz Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.1 KiB
SEO Optimization — Design Spec
Date: 2026-04-07
Domain: letsbe.biz
Stack: Next.js 16 App Router, next-intl (en/fr, as-needed prefix), Payload CMS
Current State
The site has a single metadata export in the layout with a generic title/description. No robots.txt, no sitemap, no OG tags, no hreflang, no structured data, no analytics. The services page has a static English-only metadata export. Case study pages and the homepage have no metadata at all.
1. Metadata Foundation
metadataBase
Add metadataBase: new URL('https://letsbe.biz') to src/app/(frontend)/[locale]/layout.tsx. Required for OG images and canonical URLs to resolve as absolute URLs.
i18n SEO keys
Add a meta section to both en.json and fr.json:
{
"meta": {
"home": { "title": "...", "description": "..." },
"about": { "title": "...", "description": "..." },
"services": { "title": "...", "description": "..." },
"work": {
"monaco-ocean": { "title": "...", "description": "..." },
"port-nimara": { "title": "...", "description": "..." },
"port-amador": { "title": "...", "description": "..." }
}
}
}
Per-page generateMetadata
Convert every page to use generateMetadata({ params }):
- Read locale from params
- Call
getTranslations({ locale, namespace: 'meta' })for localized title/description - Set
alternates.canonical(e.g.,https://letsbe.biz/aboutfor en,https://letsbe.biz/fr/aboutfor fr) - Set
alternates.languagesfor hreflang:en→ unprefixed pathfr→/fr/prefixed pathx-default→ unprefixed (English)
Layout metadata
Convert the layout's static metadata to generateMetadata() so it can set locale-aware defaults, metadataBase, and default OG/Twitter config.
2. Social Sharing (Open Graph / Twitter)
Default OG in layout
openGraph: {
type: 'website',
siteName: 'LetsBe.',
locale: locale === 'fr' ? 'fr_FR' : 'en_US',
images: [{ url: '/images/og-default.png', width: 1200, height: 630 }],
}
twitter: {
card: 'summary_large_image',
}
OG image
Create a 1200x630 OG default image using sharp — the logo centered on a branded background (the site's surface color with the blue accent). Place at public/images/og-default.png.
Per-page OG
Case study pages override with their hero image. Other pages use the default.
3. Crawl Infrastructure
robots.ts
src/app/robots.ts:
- Allow all user agents
- Disallow
/admin/,/api/ - Sitemap:
https://letsbe.biz/sitemap.xml
sitemap.ts
src/app/sitemap.ts:
- Static routes:
/,/about,/services - Dynamic routes:
/work/monaco-ocean,/work/port-nimara,/work/port-amador - Each entry includes both locale variants in
alternates.languages lastModifiedset to build datechangeFrequencyandpriorityset appropriately
4. Structured Data (JSON-LD)
Inject via <script type="application/ld+json"> in layout and page components.
Organization (layout)
{
"@type": "Organization",
"name": "LetsBe.",
"url": "https://letsbe.biz",
"logo": "https://letsbe.biz/images/letsbe-logo-short.png",
"sameAs": ["linkedin-url", "x-url"]
}
WebSite (homepage)
{
"@type": "WebSite",
"name": "LetsBe.",
"url": "https://letsbe.biz"
}
Service (services page)
One Service entry per pillar (Design, Software, Infrastructure).
CreativeWork (case study pages)
Per-project with title, description, image, creator → Organization.
5. Google Analytics + Consent Mode
GA4 integration
- Measurement ID:
G-LD348WF8EM - Use Next.js
@next/third-parties/googleGoogleAnalytics component (ornext/scriptif the package isn't available) - Place in the layout, render only in production (
process.env.NODE_ENV === 'production')
Consent Mode v2
Set default consent state before GA loads:
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
wait_for_update: 500,
});
This lets GA4 run in cookieless mode (modeled conversions, basic pageviews) without requiring a cookie banner. A full consent banner can be added later to unlock full measurement.
Environment variable
NEXT_PUBLIC_GA_ID=G-LD348WF8EM in .env / .env.production.
Files to Create/Modify
| File | Action |
|---|---|
src/app/(frontend)/[locale]/layout.tsx |
Convert to generateMetadata, add OG, add JSON-LD Organization |
src/app/(frontend)/[locale]/page.tsx |
Add generateMetadata, add JSON-LD WebSite |
src/app/(frontend)/[locale]/about/page.tsx |
Add generateMetadata |
src/app/(frontend)/[locale]/services/page.tsx |
Replace static metadata with generateMetadata, add JSON-LD Service |
src/app/(frontend)/[locale]/work/[slug]/page.tsx |
Add generateMetadata, add JSON-LD CreativeWork |
src/i18n/messages/en.json |
Add meta section |
src/i18n/messages/fr.json |
Add meta section |
src/app/robots.ts |
Create |
src/app/sitemap.ts |
Create |
public/images/og-default.png |
Create (generated via sharp) |
src/components/analytics/GoogleAnalytics.tsx |
Create (GA4 + consent mode) |
.env.production |
Add NEXT_PUBLIC_GA_ID |