Skip to content

Commit c43ad8f

Browse files
committed
chore(docs): sitemap
- fix broken sitemap - add additional content schema/metadata - generate page titles - fix broken robots.txt Signed-off-by: Cory Rylan <crylan@nvidia.com>
1 parent 99737bf commit c43ad8f

10 files changed

Lines changed: 205 additions & 91 deletions

File tree

projects/site/eleventy.config.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { EleventyRenderPlugin, IdAttributePlugin } from '@11ty/eleventy';
44
import EleventyPluginVite from '@11ty/eleventy-plugin-vite';
55
import litPlugin from '@lit-labs/eleventy-plugin-lit';
66

7-
import { BASE_URL } from './src/_11ty/layouts/common.js';
7+
import { BASE_URL } from './src/_11ty/layouts/metadata.js';
88
import {
99
ELEMENTS_PAGES_BASE_URL,
1010
ELEMENTS_REPO_BASE_URL,
@@ -16,6 +16,7 @@ import {
1616
} from './src/_11ty/utils/env.js';
1717
import { searchPlugin } from './src/_11ty/plugins/search.js';
1818
import { llmsTxtPlugin } from './src/_11ty/plugins/llms-txt.js';
19+
import { sitemapPlugin } from './src/_11ty/plugins/sitemap-xml.js';
1920
import { elementLoaderTransform } from './src/_11ty/transforms/element-loader.js';
2021
import { anchorGeneratorTransform } from './src/_11ty/transforms/anchor-generator.js';
2122
import { htmlMinifyTransform } from './src/_11ty/transforms/html-minify.js';
@@ -104,8 +105,6 @@ export default function (eleventyConfig) {
104105
eleventyConfig.addPassthroughCopy({ '../themes/dist/bundles': 'local-bundles/themes' });
105106
eleventyConfig.addPassthroughCopy({ '../styles/dist/bundles': 'local-bundles/styles' });
106107

107-
eleventyConfig.addPassthroughCopy('src/robots.txt');
108-
109108
// Configure Lit SSR plugin for web components
110109
eleventyConfig.addPlugin(litPlugin, {
111110
mode: 'worker',
@@ -174,6 +173,11 @@ export default function (eleventyConfig) {
174173
eleventyConfig.addPlugin(llmsTxtPlugin);
175174
}
176175

176+
// https://www.sitemaps.org
177+
if (process.env.ELEVENTY_RUN_MODE === 'build') {
178+
eleventyConfig.addPlugin(sitemapPlugin);
179+
}
180+
177181
// Set custom markdown library
178182
eleventyConfig.setLibrary('md', markdown);
179183

projects/site/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@
6363
"PAGES_BASE_URL": {
6464
"external": true,
6565
"default": "/elements/"
66+
},
67+
"ELEMENTS_SITE_URL": {
68+
"external": true,
69+
"default": "https://nvidia.github.io"
6670
}
6771
}
6872
},

projects/site/public/robots.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
User-agent: *
2-
Disallow:
2+
Allow: /
3+
Disallow: /assets/
4+
Disallow: /.pagefind/
5+
6+
Sitemap: https://nvidia.github.io/elements/sitemap.xml

projects/site/src/_11ty/layouts/common.js

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
/* eslint-env node */
22
/* global process */
33

4-
import { join } from 'node:path';
5-
import { ELEMENTS_PAGES_BASE_URL, ELEMENTS_PLAYGROUND_BASE_URL, ELEMENTS_REPO_BASE_URL } from '../utils/env.js';
6-
7-
export const BASE_URL = join('/', process.env.PAGES_BASE_URL ?? '', '/');
4+
import { ELEMENTS_PLAYGROUND_BASE_URL, ELEMENTS_REPO_BASE_URL } from '../utils/env.js';
5+
import { escapeAttr, resolvePageMeta, renderJsonLd, BASE_URL } from './metadata.js';
86

97
/**
108
* This renders the base head element with all the common styles and scripts needed for ALL PAGES.
119
* Page specific resources should not be placed here.
1210
*/
13-
export const renderBaseHead = data => /* html */ `
11+
export const renderBaseHead = data => {
12+
const meta = resolvePageMeta(data);
13+
const ogType = meta.url === '/' ? 'website' : 'article';
14+
return /* html */ `
1415
<meta charset="UTF-8">
1516
<meta name="viewport" content="width=device-width, initial-scale=1.0">
1617
<meta name="robots" content="all">
1718
<base href="${BASE_URL}" />
18-
<title data-pagefind-meta="title">${data.title}</title>
19-
<meta name="description" content="Elements - ${data.title}">
20-
<meta property="og:title" content="Elements - ${data.title}" >
21-
<meta property="og:url" content="${data.page?.url ?? ''}">
22-
<meta property="og:description" content="NVIDIA Elements is a flexible, framework-agnostic design system and toolkit that empowers teams to build exceptional user experiences.">
23-
<meta property="og:image" content="/favicon.svg">
24-
<meta property="og:site_name" content="${ELEMENTS_PAGES_BASE_URL}/">
25-
<meta property="og:type" content="website">
19+
<title data-pagefind-meta="title">${escapeAttr(meta.title)}</title>
20+
<meta name="description" content="${escapeAttr(meta.description)}">
21+
<link rel="canonical" href="${meta.canonicalUrl}">
22+
<meta property="og:title" content="${escapeAttr(meta.title)}">
23+
<meta property="og:url" content="${meta.canonicalUrl}">
24+
<meta property="og:description" content="${escapeAttr(meta.description)}">
25+
<meta property="og:image" content="${meta.ogImage}">
26+
<meta property="og:site_name" content="NVIDIA Elements">
27+
<meta property="og:type" content="${ogType}">
2628
<link rel="icon" href="/favicon.svg">
29+
${renderJsonLd(data, meta)}
2730
${renderGlobalsScript(data)}
2831
<style>
2932
@import '@nvidia-elements/themes/fonts/inter.css';
@@ -99,6 +102,7 @@ export const renderBaseHead = data => /* html */ `
99102
globalThis.document.documentElement.removeAttribute('no-js');
100103
</script>
101104
`;
105+
};
102106

103107
export const renderDocsNav = data => /* html */ `
104108
<nve-tree id="docs-nav" data-pagefind-ignore="all" behavior-expand selectable="single">
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { join } from 'node:path';
2+
import { ELEMENTS_SITE_URL } from '../utils/env.js';
3+
import { siteData } from '../../index.11tydata.js';
4+
5+
export const BASE_URL = join('/', process.env.PAGES_BASE_URL ?? '', '/');
6+
7+
const SITE_ORIGIN = ELEMENTS_SITE_URL.replace(/\/$/, '');
8+
const PATH_PREFIX = BASE_URL.replace(/\/$/, '');
9+
10+
const BREADCRUMB_TERMS = {
11+
api: 'API',
12+
cli: 'CLI',
13+
mcp: 'MCP',
14+
ssr: 'SSR',
15+
i18n: 'i18n',
16+
ui: 'UI',
17+
nextjs: 'Next.js',
18+
solidjs: 'SolidJS',
19+
nuxt: 'Nuxt',
20+
typescript: 'TypeScript',
21+
go: 'Go',
22+
vue: 'Vue',
23+
react: 'React',
24+
svelte: 'Svelte',
25+
preact: 'Preact',
26+
lit: 'Lit',
27+
hugo: 'Hugo',
28+
angular: 'Angular',
29+
monaco: 'Monaco',
30+
jsdoc: 'JSDoc',
31+
json: 'JSON',
32+
html: 'HTML',
33+
css: 'CSS',
34+
nvidia: 'NVIDIA',
35+
npm: 'npm'
36+
};
37+
38+
export function escapeAttr(value) {
39+
return String(value ?? '')
40+
.replace(/&/g, '&amp;')
41+
.replace(/</g, '&lt;')
42+
.replace(/>/g, '&gt;')
43+
.replace(/"/g, '&quot;')
44+
.replace(/'/g, '&#39;');
45+
}
46+
47+
function titleCaseSegment(segment) {
48+
if (BREADCRUMB_TERMS[segment]) return BREADCRUMB_TERMS[segment];
49+
return segment
50+
.split('-')
51+
.map(part => BREADCRUMB_TERMS[part] ?? part.charAt(0).toUpperCase() + part.slice(1))
52+
.join(' ');
53+
}
54+
55+
function findElementByTag(tag) {
56+
if (!tag) return null;
57+
return siteData.elements.find(e => e.name === tag) ?? null;
58+
}
59+
60+
export function resolvePageMeta(data) {
61+
const url = data.page?.url ?? '/';
62+
const title = data.title ?? 'NVIDIA Elements';
63+
const tag = data.tag ?? data.component?.data?.tag;
64+
const element = findElementByTag(tag);
65+
const componentDescription = element?.manifest?.description?.trim();
66+
67+
let description;
68+
if (data.description) {
69+
description = data.description;
70+
} else if (data.isApiTab && componentDescription) {
71+
description = `${componentDescription} — API reference for <${tag}>.`;
72+
} else if (data.isExamplesTab && componentDescription) {
73+
description = `${componentDescription} — Interactive examples for <${tag}>.`;
74+
} else if (componentDescription) {
75+
description = componentDescription;
76+
} else {
77+
description = `Documentation for ${title} in NVIDIA Elements, the framework-agnostic design system for AI/ML factories.`;
78+
}
79+
80+
const canonicalUrl = `${SITE_ORIGIN}${PATH_PREFIX}${url}`;
81+
const ogImage = `${SITE_ORIGIN}${PATH_PREFIX}/favicon.svg`;
82+
return { title, description, canonicalUrl, ogImage, url };
83+
}
84+
85+
function jsonLdEncode(value) {
86+
return JSON.stringify(value).replace(/<\//g, '<\\/');
87+
}
88+
89+
export function renderJsonLd(data, meta) {
90+
const isDocs = meta.url.startsWith('/docs/');
91+
const articleType = isDocs ? 'TechArticle' : 'WebPage';
92+
const date = data.page?.date instanceof Date ? data.page.date.toISOString() : new Date().toISOString();
93+
94+
const article = {
95+
'@context': 'https://schema.org',
96+
'@type': articleType,
97+
headline: meta.title,
98+
description: meta.description,
99+
url: meta.canonicalUrl,
100+
mainEntityOfPage: meta.canonicalUrl,
101+
inLanguage: 'en',
102+
image: meta.ogImage,
103+
datePublished: date,
104+
dateModified: date,
105+
publisher: { '@type': 'Organization', name: 'NVIDIA', url: SITE_ORIGIN },
106+
author: { '@type': 'Organization', name: 'NVIDIA' }
107+
};
108+
109+
const segments = meta.url.split('/').filter(Boolean);
110+
const articleScript = `<script type="application/ld+json">${jsonLdEncode(article)}</script>`;
111+
if (segments.length === 0) {
112+
return articleScript;
113+
}
114+
115+
const itemListElement = [{ '@type': 'ListItem', position: 1, name: 'Home', item: `${SITE_ORIGIN}${PATH_PREFIX}/` }];
116+
let cumulative = '';
117+
segments.forEach((seg, i) => {
118+
cumulative += `/${seg}`;
119+
itemListElement.push({
120+
'@type': 'ListItem',
121+
position: i + 2,
122+
name: titleCaseSegment(seg),
123+
item: `${SITE_ORIGIN}${PATH_PREFIX}${cumulative}/`
124+
});
125+
});
126+
127+
const breadcrumb = {
128+
'@context': 'https://schema.org',
129+
'@type': 'BreadcrumbList',
130+
itemListElement
131+
};
132+
return `${articleScript}\n <script type="application/ld+json">${jsonLdEncode(breadcrumb)}</script>`;
133+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { promises as fsp } from 'node:fs';
2+
import { BASE_URL } from '../layouts/metadata.js';
3+
import { ELEMENTS_SITE_URL } from '../utils/env.js';
4+
5+
const SITE_ORIGIN = ELEMENTS_SITE_URL.replace(/\/$/, '');
6+
const PATH_PREFIX = BASE_URL.replace(/\/$/, '');
7+
const EXCLUDED_PREFIXES = ['/docs/changelog/', '/docs/metrics/', '/examples/', '/404'];
8+
9+
function isPublishable(url) {
10+
if (!url) return false;
11+
if (!url.endsWith('/') && !url.endsWith('.html')) return false;
12+
if (EXCLUDED_PREFIXES.some(prefix => url.startsWith(prefix))) return false;
13+
return true;
14+
}
15+
16+
export function sitemapPlugin(eleventyConfig) {
17+
eleventyConfig.on('eleventy.after', async ({ results } = {}) => {
18+
const urls = [...new Set((results ?? []).map(r => r.url).filter(isPublishable))].sort();
19+
const lastmod = new Date().toISOString();
20+
const entries = urls.map(url => {
21+
const loc = `${SITE_ORIGIN}${PATH_PREFIX}${url}`;
22+
return ['<url>', `<loc>${loc}</loc>`, `<lastmod>${lastmod}</lastmod>`, '</url>'].join('\n');
23+
});
24+
25+
const xml = [
26+
'<?xml version="1.0" encoding="UTF-8"?>',
27+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
28+
...entries,
29+
'</urlset>',
30+
''
31+
].join('\n');
32+
33+
await fsp.mkdir('./.11ty-vite/public/', { recursive: true });
34+
await fsp.writeFile('./.11ty-vite/public/sitemap.xml', xml, 'utf-8');
35+
});
36+
}

projects/site/src/_11ty/utils/env.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export const ELEMENTS_REGISTRY_URL = process.env.ELEMENTS_REGISTRY_URL ?? '';
88
export const ELEMENTS_ASSETS_CDN_BASE_URL = process.env.ELEMENTS_ASSETS_CDN_BASE_URL ?? '';
99
export const ELEMENTS_ESM_CDN_BASE_URL = process.env.ELEMENTS_ESM_CDN_BASE_URL ?? '';
1010
export const ELEMENTS_CDN_BASE_URL = process.env.ELEMENTS_CDN_BASE_URL ?? '';
11+
export const ELEMENTS_SITE_URL = process.env.ELEMENTS_SITE_URL ?? 'https://nvidia.github.io';

projects/site/src/docs/elements/_tabs/examples.11ty.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export async function render(data) {
5151
data.tag = componentData.tag;
5252
data.title = componentData.title;
5353
data.page.fileSlug = componentData.page.fileSlug;
54+
data.isExamplesTab = true;
5455

5556
return /* html */ `
5657
<style scoped>

projects/site/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
{
33
title: 'Getting Started',
4+
description: 'Get started with NVIDIA Elements: install Web Components, themes, and starter templates for building framework-agnostic UI for AI/ML factories.',
45
layout: 'docs.11ty.js'
56
}
67
---

projects/site/src/robots.txt

Lines changed: 0 additions & 74 deletions
This file was deleted.

0 commit comments

Comments
 (0)