From 863cb7fc38d1db960caec143b38b4a7bd4895630 Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Sun, 6 Sep 2026 16:19:02 +0300 Subject: [PATCH] feat(seo): add Schema.org JSON-LD structured data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The site had no structured data at all. The three itemProp tags in Meta.tsx are orphaned Microdata with no parent itemscope/itemtype, so search engines ignore them entirely. Adds, via a new optional `jsonLd` prop on Layout so everything renders inside the existing rather than a second one: - Organization + WebSite on the homepage only, since they describe the site as a whole rather than each page - SoftwareSourceCode on library pages - BreadcrumbList + TechArticle on component and design guideline pages Breadcrumb URLs and names are locale-aware: they use getCanonicalUrlForLocale and the same translations the navigation uses, so /ru pages emit "Компоненты > UIKit > Button" pointing at /ru URLs. Deliberate omissions: - HowTo: deprecated since September 2023. - FAQPage: Google retired FAQ rich results in May 2026. - WebSite.potentialAction/SearchAction: the site has no search endpoint, so declaring one would describe something that does not exist. - datePublished/dateModified on TechArticle: readme content is fetched from GitHub raw URLs, which carry no reliable authoring date, and an invented date is worse than none. - The npm organisation in sameAs: not linked from the site and could not be verified. The GitHub, Telegram and Figma URLs are the ones the site itself links to. Two details differ from the audit's proposal, which assumed fields that do not exist: LibConfig has no `description` (the library meta description from i18n is used instead) and no plain `githubUrl` (it has `githubId`, e.g. "gravity-ui/uikit"). Verified against a production build: JSON-LD is present and parses on homepage, library, component and design pages in en and ru, absent where nothing applies, and all page types return 200. Build, lint and typecheck pass. Run Google's Rich Results Test after deploy to confirm breadcrumb eligibility. E2E not run - Playwright browsers are not installed locally. Co-Authored-By: Claude Opus 5 (1M context) --- src/components/Layout/Layout.tsx | 11 +++ .../components/[libId]/[componentId].tsx | 25 ++++++ src/pages/design/[sectionId]/[articleId].tsx | 25 ++++++ src/pages/index.tsx | 4 +- src/pages/libraries/[libId]/index.tsx | 14 ++- src/utils/structuredData.ts | 89 +++++++++++++++++++ 6 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 src/utils/structuredData.ts diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 779687f77152..edee5d2e291c 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -8,6 +8,7 @@ import {CONTENT_WRAPPER_ID, DEFAULT_THEME, MENU_ID} from '../../constants'; import {EnvironmentContext} from '../../contexts'; import {useLocale} from '../../hooks/useLocale'; import {block} from '../../utils'; +import type {JsonLd} from '../../utils/structuredData'; import {CustomScrollbar} from '../CustomScrollbar'; import {Footer} from '../Footer/Footer'; import {Menu} from '../Menu/Menu'; @@ -31,6 +32,8 @@ export type LayoutProps = { noScroll?: boolean; meta?: MetaProps; hideLocalePicker?: boolean; + /** Schema.org blocks rendered as JSON-LD inside . */ + jsonLd?: JsonLd[]; }; export const Layout: React.FC = ({ @@ -43,6 +46,7 @@ export const Layout: React.FC = ({ noScroll = false, hideLocalePicker = false, meta = {}, + jsonLd, }) => { const locale = useLocale(); @@ -104,6 +108,13 @@ export const Layout: React.FC = ({ {`Gravity UI${title ? ` – ${title}` : ''}`} + {jsonLd?.map((item, index) => ( +