function generateJsonLd(site, seo, req) { const siteUrl = site.url || 'https://pengzhan.dev'; const person = { '@type': 'Person', '@id': `${siteUrl}/#person`, name: (site.author && site.author.name) || site.title, url: siteUrl, jobTitle: (site.author && site.author.subtitle) || 'Software Engineer', sameAs: (site.social || []).map(s => s.url).filter(Boolean) }; const website = { '@type': 'WebSite', '@id': `${siteUrl}/#website`, url: siteUrl, name: site.title, description: site.tagline, publisher: { '@id': `${siteUrl}/#person` }, potentialAction: [ { '@type': 'SearchAction', target: { '@type': 'EntryPoint', urlTemplate: `${siteUrl}/search?q={search_term_string}` }, 'query-input': 'required name=search_term_string' } ] }; const graph = [person, website]; if (seo.type === 'article') { const blogPosting = { '@type': 'BlogPosting', '@id': `${seo.canonicalUrl}#article`, isPartOf: { '@id': `${siteUrl}/#website` }, author: { '@id': `${siteUrl}/#person` }, headline: seo.title, datePublished: seo.publishedTime || new Date().toISOString(), dateModified: seo.modifiedTime || seo.publishedTime || new Date().toISOString(), mainEntityOfPage: seo.canonicalUrl, description: seo.description, image: seo.ogImage, articleSection: seo.category || 'Tech', keywords: Array.isArray(seo.tags) ? seo.tags.join(', ') : seo.tags }; graph.push(blogPosting); } // Breadcrumbs const breadcrumbItems = [ { '@type': 'ListItem', position: 1, name: 'Home', item: siteUrl } ]; if (req.path !== '/') { breadcrumbItems.push({ '@type': 'ListItem', position: 2, name: seo.title, item: seo.canonicalUrl }); } graph.push({ '@type': 'BreadcrumbList', '@id': `${seo.canonicalUrl}#breadcrumb`, itemListElement: breadcrumbItems }); return JSON.stringify({ '@context': 'https://schema.org', '@graph': graph }); } function seoMiddleware(siteConfig) { return (req, res, next) => { res.locals.site = siteConfig.site; res.locals.navigation = siteConfig.navigation; res.locals.social = siteConfig.social; res.locals.currentPath = req.path; // Helper to generate SEO metadata res.locals.seo = { title: siteConfig.site.title, description: siteConfig.site.tagline, canonicalUrl: `${siteConfig.site.url}${req.path}`, ogImage: `${siteConfig.site.url}/images/og-default.png`, type: 'website', tags: [], category: '' }; res.locals.getJsonLd = () => generateJsonLd(siteConfig.site, res.locals.seo, req); next(); }; } module.exports = seoMiddleware;