Files
bayarea/scripts/prerender-routes.ts
2026-03-25 20:07:27 -05:00

207 lines
7.1 KiB
TypeScript

import fs from 'node:fs/promises';
import path from 'node:path';
import { blogPostData, locationData, serviceData } from '../src/data/seoData';
type RouteMeta = {
route: string;
title: string;
description: string;
canonicalUrl: string;
keywords?: string[];
schema?: Record<string, unknown>;
};
const DIST_DIR = path.resolve(process.cwd(), 'dist');
const BASE_URL = 'https://bayareait.services';
const DEFAULT_OG_IMAGE = `${BASE_URL}/logo.svg`;
const staticRoutes: RouteMeta[] = [
{
route: '/',
title: 'IT Service & IT Support for Businesses in Corpus Christi, TX',
description:
'Reliable IT support and IT services for businesses in Corpus Christi, TX. Fast response, outsourced IT support and help desk solutions.',
canonicalUrl: `${BASE_URL}/`,
keywords: ['IT Service', 'IT Support', 'Corpus Christi', 'IT Help Desk'],
schema: {
'@context': 'https://schema.org',
'@type': 'ITService',
name: 'Bay Area IT',
url: BASE_URL,
telephone: '+1-361-765-8400',
areaServed: ['Corpus Christi', 'Portland', 'Rockport', 'Aransas Pass', 'Kingsville'],
},
},
{
route: '/about',
title: 'About Bay Area IT | Local IT Support in Corpus Christi',
description:
'Learn about Bay Area IT, a local IT partner serving Corpus Christi and the Coastal Bend with practical support, reliable service, and over 25 years of experience.',
canonicalUrl: `${BASE_URL}/about`,
schema: {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Bay Area IT',
url: BASE_URL,
},
},
{
route: '/services',
title: 'IT Services | Bay Area IT Support, Email, Networking and Web',
description:
'Explore Bay Area IT services for Corpus Christi businesses, including help desk support, business email, networking, hardware, web design, and day-to-day IT support.',
canonicalUrl: `${BASE_URL}/services`,
},
{
route: '/blog',
title: 'Blog | Bay Area IT Insights for Corpus Christi Businesses',
description:
'Read practical IT guidance for Corpus Christi and Coastal Bend businesses, from managed IT support and costs to business email and local service coverage.',
canonicalUrl: `${BASE_URL}/blog`,
},
{
route: '/contact',
title: 'Contact Bay Area IT | Free IT Assessment in Corpus Christi',
description:
'Talk to Bay Area IT about managed IT support, help desk coverage, business email, networking, and technology support across Corpus Christi and the Coastal Bend.',
canonicalUrl: `${BASE_URL}/contact`,
},
{
route: '/locations',
title: 'IT Support Service Areas - Corpus Christi & Coastal Bend, TX',
description:
'Bay Area IT provides IT support and IT services throughout the Coastal Bend. View all cities we serve in the Corpus Christi area.',
canonicalUrl: `${BASE_URL}/locations`,
},
{
route: '/privacy-policy',
title: 'Privacy Policy | Bay Area IT',
description:
'Read how Bay Area IT collects, uses, and protects information submitted through this website.',
canonicalUrl: `${BASE_URL}/privacy-policy`,
},
{
route: '/terms-of-service',
title: 'Terms of Service | Bay Area IT',
description:
'Review the Bay Area IT terms covering use of this website and our IT support services.',
canonicalUrl: `${BASE_URL}/terms-of-service`,
},
];
const dynamicRoutes: RouteMeta[] = [
...locationData.map((item) => ({
route: `/${item.slug}`,
title: item.title,
description: item.description,
canonicalUrl: `${BASE_URL}/${item.slug}`,
keywords: item.keywords,
schema: {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
name: 'Bay Area IT',
url: `${BASE_URL}/${item.slug}`,
areaServed: item.city,
},
})),
...serviceData.map((item) => ({
route: `/${item.slug}`,
title: item.title,
description: item.description,
canonicalUrl: `${BASE_URL}/${item.slug}`,
keywords: item.keywords,
schema: {
'@context': 'https://schema.org',
'@type': 'Service',
name: item.h1,
provider: {
'@type': 'Organization',
name: 'Bay Area IT',
},
url: `${BASE_URL}/${item.slug}`,
},
})),
...blogPostData
.filter((item) => !item.redirect)
.map((item) => ({
route: `/${item.slug}`,
title: item.title,
description: item.description,
canonicalUrl: `${BASE_URL}/${item.slug}`,
keywords: item.keywords,
schema: {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: item.h1,
description: item.description,
url: `${BASE_URL}/${item.slug}`,
publisher: {
'@type': 'Organization',
name: 'Bay Area IT',
},
},
})),
];
function escapeHtml(value: string) {
return value
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function buildHead(meta: RouteMeta) {
const keywords = meta.keywords?.length
? `\n <meta name="keywords" content="${escapeHtml(meta.keywords.join(', '))}" />`
: '';
const schema = meta.schema
? `\n <script type="application/ld+json">${JSON.stringify(meta.schema)}</script>`
: '';
return ` <title>${escapeHtml(meta.title)}</title>
<meta name="description" content="${escapeHtml(meta.description)}" />${keywords}
<link rel="canonical" href="${meta.canonicalUrl}" />
<meta property="og:title" content="${escapeHtml(meta.title)}" />
<meta property="og:description" content="${escapeHtml(meta.description)}" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Bay Area IT" />
<meta property="og:url" content="${meta.canonicalUrl}" />
<meta property="og:image" content="${DEFAULT_OG_IMAGE}" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="${escapeHtml(meta.title)}" />
<meta name="twitter:description" content="${escapeHtml(meta.description)}" />${schema}`;
}
function injectHead(template: string, meta: RouteMeta) {
const withoutTitle = template.replace(/<title>[\s\S]*?<\/title>/i, '');
return withoutTitle.replace('</head>', `${buildHead(meta)}\n </head>`);
}
async function writeRouteHtml(template: string, meta: RouteMeta) {
const html = injectHead(template, meta);
const cleanRoute = meta.route === '/' ? '' : meta.route.replace(/^\/+/, '');
const filePath =
meta.route === '/'
? path.join(DIST_DIR, 'index.html')
: path.join(DIST_DIR, cleanRoute, 'index.html');
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, html, 'utf8');
}
async function main() {
const templatePath = path.join(DIST_DIR, 'index.html');
const template = await fs.readFile(templatePath, 'utf8');
const allRoutes = [...staticRoutes, ...dynamicRoutes];
await Promise.all(allRoutes.map((meta) => writeRouteHtml(template, meta)));
console.log(`Prerendered ${allRoutes.length} route HTML files.`);
}
main().catch((error) => {
console.error('Failed to prerender route HTML files.');
console.error(error);
process.exit(1);
});