All checks were successful
CI — Website / Website — typecheck, build (pull_request) Successful in 1m2s
Give Google unique snippets and FAQ rich results, and keep robots/sitemap from being cached as HTML. Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
2.4 KiB
TypeScript
87 lines
2.4 KiB
TypeScript
import faq from './faq.json';
|
|
import routesFile from './seo.routes.json';
|
|
import { CHROME_STORE, REPO, VSCODE_MARKETPLACE } from './links';
|
|
|
|
export const SITE_ORIGIN = routesFile.origin;
|
|
|
|
export type SeoRoute = {
|
|
path: string;
|
|
title: string;
|
|
description: string;
|
|
index: boolean;
|
|
};
|
|
|
|
export const INDEXABLE_ROUTES: SeoRoute[] = routesFile.routes.map((r) => ({
|
|
...r,
|
|
index: true,
|
|
}));
|
|
|
|
export const NOT_FOUND_SEO: SeoRoute = {
|
|
path: '',
|
|
title: 'Page not found — LexAI',
|
|
description: 'That address is not a LexAI page.',
|
|
index: false,
|
|
};
|
|
|
|
const byPath = new Map(INDEXABLE_ROUTES.map((r) => [r.path, r]));
|
|
|
|
export function seoForPath(pathname: string): SeoRoute {
|
|
const normalized = pathname.replace(/\/+$/, '') || '/';
|
|
return byPath.get(normalized) ?? NOT_FOUND_SEO;
|
|
}
|
|
|
|
export function canonicalUrl(path: string): string {
|
|
if (!path || path === '/') return `${SITE_ORIGIN}/`;
|
|
return `${SITE_ORIGIN}${path.startsWith('/') ? path : `/${path}`}`;
|
|
}
|
|
|
|
function softwareGraph() {
|
|
return {
|
|
'@type': 'SoftwareApplication',
|
|
name: 'LexAI',
|
|
applicationCategory: 'BrowserApplication',
|
|
applicationSubCategory: 'Writing assistance',
|
|
operatingSystem: 'Chrome, VS Code, Windows, macOS, Linux',
|
|
url: `${SITE_ORIGIN}/`,
|
|
downloadUrl: [CHROME_STORE, VSCODE_MARKETPLACE],
|
|
license: `${REPO}/src/branch/main/LICENSE`,
|
|
sameAs: [REPO, CHROME_STORE, VSCODE_MARKETPLACE],
|
|
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
|
};
|
|
}
|
|
|
|
export function jsonLdForPath(pathname: string): Record<string, unknown> {
|
|
const seo = seoForPath(pathname);
|
|
const url = seo.index ? canonicalUrl(seo.path) : `${SITE_ORIGIN}${pathname}`;
|
|
const graph: unknown[] = [
|
|
{
|
|
'@type': 'WebSite',
|
|
name: 'LexAI',
|
|
url: `${SITE_ORIGIN}/`,
|
|
description: seoForPath('/').description,
|
|
},
|
|
softwareGraph(),
|
|
];
|
|
|
|
if (seo.path === '/') {
|
|
graph.push({
|
|
'@type': 'FAQPage',
|
|
mainEntity: faq.map((item) => ({
|
|
'@type': 'Question',
|
|
name: item.q,
|
|
acceptedAnswer: { '@type': 'Answer', text: item.a },
|
|
})),
|
|
});
|
|
} else if (seo.index) {
|
|
graph.push({
|
|
'@type': 'BreadcrumbList',
|
|
itemListElement: [
|
|
{ '@type': 'ListItem', position: 1, name: 'Home', item: `${SITE_ORIGIN}/` },
|
|
{ '@type': 'ListItem', position: 2, name: seo.title, item: url },
|
|
],
|
|
});
|
|
}
|
|
|
|
return { '@context': 'https://schema.org', '@graph': graph };
|
|
}
|