The public site is the marketing surface — SEO matters. This page documents the conventions and the surface area that affects discoverability.
Every public page exports a Next.js Metadata object with title, description, OG image,
canonical URL, robots, and (where relevant) Twitter card. Two patterns:
export const metadata: Metadata = { … }.[slug] params export
export async function generateMetadata({ params }) { … } and pull title/description from
the same data source the page renders (WordPress or MongoDB), so SEO and content never
drift.Helper utilities for building consistent metadata and structured data live in
lib/seo-config.ts:
coreMetadata — shared base metadata (titles, OG defaults) merged into page metadata.generatePageMetadata({ … }) — builds a Next.js Metadata object for a page.StructuredData — React component that renders a JSON-LD <script> for a given schema.generateBreadcrumbSchema(...), generateFAQSchema(...), plus the prebuilt
organizationSchema, websiteSchema, softwareApplicationSchema, productSchema,
localBusinessSchema, brandSchema, jobPostingSchema, and more.Content-specific schema lives alongside the data layer: lib/post-schema.ts (blog posts),
lib/faq.ts (FAQ data), and lib/glossary/schema-org.ts (glossary terms). The only file
under lib/seo/ is lib/seo/indexnow.ts (IndexNow ping helper).
JSON-LD is injected via the StructuredData component from lib/seo-config.ts (which renders a
<script type="application/ld+json">). Blog posts additionally use the RankMathJsonLd
component to pass through Rank Math SEO markup from WordPress. The shapes we emit:
| Shape | Where |
|---|---|
Organization | Root layout (every page), via StructuredData schema={organizationSchema}. |
WebSite + SearchAction | Root layout (websiteSchema). |
SoftwareApplication, Product, LocalBusiness, Brand | Root layout (prebuilt schemas in lib/seo-config.ts). |
Article | Blog posts (lib/post-schema.ts) + glossary Article (lib/glossary/schema-org.ts). |
FAQPage | Glossary terms, FAQ sections (generateFAQSchema, glossary buildFaqJsonLd). |
DefinedTerm + DefinedTermSet | Glossary terms / glossary index (lib/glossary/schema-org.ts). |
BreadcrumbList | Any page with a parent (uses generateBreadcrumbSchema in lib/seo-config.ts; glossary emits its own). |
JobPosting | /careers/* job pages (jobPostingSchema). |
Validate at https://search.google.com/test/rich-results before shipping changes.
All permanent (308) redirects are declared in the redirects() function in
next.config.ts. Next.js applies the first matching rule, so specific
paths must precede catch-alls. Add a redirect here whenever a URL is renamed, removed, or
linked from elsewhere with a path the app doesn't serve.
Notable groups:
/workspace/* and /auth/* → the workspace subdomain (gated on
WORKSPACE_SUBDOMAIN_URL)./key-features/* and product renames — e.g. /dspm → /data-security-intelligence,
/key-features/ai-data-detection-response (and the advanced- alias) → /ai-data-detection-response./solutions/by-use-case/* and
/solutions/by-industry/* structure that was never built, which Ahrefs flagged as the
dominant 404 source (each link repeated across ~179 pages). The nav copy is intentionally
retained; the dead paths redirect to live destinations instead:
/solutions/by-industry/finance → /industry/finance, /solutions/by-industry/healthcare
→ /industry/healthcare (the only two industries with dedicated pages)./solutions/by-industry/:slug* (manufacturing, retail-travel, insurance, section landing)
→ /industries./solutions/by-use-case/:slug* and /solutions → /use-cases (label→slug mapping is
fuzzy, so the index is the safe target)./compliance/gdpr and /compliance/hipaa → /compliance (no dedicated pages yet).Canonicalization. Every indexable page should emit a self-referential
alternates.canonical. Query-string variants of the same page must point at the bare path so
search engines consolidate them — Ahrefs flagged the careers application form
(/careers/apply?id=…&role=…) as duplicate content without a canonical. Because that page is a
Client Component (it reads the query string), the canonical is set in a sibling server layout,
app/careers/apply/layout.tsx, which pins
canonical: "/careers/apply" for every variant.
app/(sitemaps)/* — dynamically generated sitemap routes. The index is
sitemap-index.xml; it references the per-section children:
sitemap-authors.xml, sitemap-blogs.xml, sitemap-careers.xml,
sitemap-datasheets.xml, sitemap-glossary.xml, sitemap-industries.xml,
sitemap-key-features.xml, sitemap-news.xml, sitemap-platform.xml,
sitemap-use-cases.xml, sitemap-videos.xml, sitemap-webinars.xml.sitemap.xml, sitemap-pages.xml, or sitemap-posts.xml.)app/sitemap.ts — a separate Next.js root static sitemap (the framework-native
/sitemap.xml convention) covering the core static routes.app/robots.txt — a static robots.txt (not a generated robots.ts). It Disallows
/api/, /admin/, /tools/, /workspace/, /_next/, /examples/, plus honeypot paths.
A per-UA Allow is set for GPTBot, ChatGPT-User, Claude-Web, and Google-Extended; the
default User-agent: * is Allow: /. The Sitemap: directive points at
sitemap-index.xml. AI bots are welcome (we want to be cited).| Tool | Purpose | Where it's loaded |
|---|---|---|
| Google Analytics 4 | Page views, conversion events | components/ConsentAwareAnalytics.tsx (consent-gated) |
| Microsoft Clarity | Session replays, heatmaps | Same, via NEXT_PUBLIC_MICROSOFT_CLARITY_ID |
| Vercel Analytics | Edge / Web Vitals | components/ConsentAwareAnalytics.tsx (consent-gated; the raw @vercel/analytics import in app/layout.tsx is commented out) |
| Sentry | Errors + performance | Production-only, gated via shouldEnableSentry() |
| rb2b | Anonymous visitor identification | Same, via NEXT_PUBLIC_RB2B_* |
| Frase | AI-crawler visit tracking | proxy.ts — fires for known bot UAs |
All client-side analytics are gated by the consent manager
(components/privacy/ConsentManager.tsx). Server-side telemetry (Sentry) is always on in prod.
The site is deliberately hospitable to AI crawlers:
robots.txt sets explicit Allow rules for GPTBot, ChatGPT-User, Claude-Web, and
Google-Extended (and User-agent: * is Allow: /, so other crawlers are permitted too).proxy.ts logs every AI-bot visit to Frase so we can measure citation rate.If a future product change needs to opt OUT of AI ingestion on specific pages, set
<meta name="robots" content="noai, noimageai"> in the page's metadata.
Track in PageSpeed Insights (PAGESPEED_API_KEY env) and Search Console field data
(GSC_SITE_URL).