diff --git a/app/[locale]/blog/[slug]/page.tsx b/app/[locale]/blog/[slug]/page.tsx index 2f378fa1..394f83a5 100644 --- a/app/[locale]/blog/[slug]/page.tsx +++ b/app/[locale]/blog/[slug]/page.tsx @@ -134,11 +134,14 @@ export default async function BlogPost({ params }: BlogPostProps) {
{getReadingTime(rawTextContent)} min read @@ -171,11 +174,14 @@ export default async function BlogPost({ params }: BlogPostProps) {
{getReadingTime(rawTextContent)} min read diff --git a/app/[locale]/blog/page.tsx b/app/[locale]/blog/page.tsx index 2144da2c..c12e171e 100644 --- a/app/[locale]/blog/page.tsx +++ b/app/[locale]/blog/page.tsx @@ -198,11 +198,14 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
diff --git a/app/stats/api/send/route.ts b/app/stats/api/send/route.ts index 3acd30f4..3f94b7f8 100644 --- a/app/stats/api/send/route.ts +++ b/app/stats/api/send/route.ts @@ -24,14 +24,34 @@ export async function POST(request: NextRequest) { return NextResponse.json({ status: 'ignored_in_dev' }, { status: 200 }); } - const body = await request.json(); + let body; + try { + body = await request.json(); + } catch (parseError) { + logger.warn('Received malformed or empty JSON in analytics proxy'); + return NextResponse.json({ status: 'ignored_bad_payload' }, { status: 200 }); + } + + if (!body || typeof body !== 'object') { + logger.warn('Received invalid body type in analytics proxy', { type: typeof body }); + return NextResponse.json({ status: 'ignored_invalid_body' }, { status: 200 }); + } + const { type, payload } = body; + if (!type || !payload) { + logger.warn('Received incomplete analytics payload', { + hasType: !!type, + hasPayload: !!payload + }); + return NextResponse.json({ status: 'ignored_incomplete_payload' }, { status: 200 }); + } + // Inject the secret websiteId from server config const websiteId = config.analytics.umami.websiteId; if (!websiteId) { logger.warn('Umami tracking received but no Website ID configured on server'); - return NextResponse.json({ status: 'ignored' }, { status: 200 }); + return NextResponse.json({ status: 'ignored_no_config' }, { status: 200 }); } // Prepare the enhanced payload with the secret ID diff --git a/components/JsonLd.tsx b/components/JsonLd.tsx index 042f4093..150ebd08 100644 --- a/components/JsonLd.tsx +++ b/components/JsonLd.tsx @@ -7,16 +7,14 @@ interface JsonLdProps { export default function JsonLd({ id, data }: JsonLdProps) { // If data is provided, use it. Otherwise, use the default Organization + WebSite schema. - const schemaData = data || [ + const rawData = data || [ { '@context': 'https://schema.org', '@type': 'Organization', name: 'KLZ Cables', url: 'https://klz-cables.com', logo: 'https://klz-cables.com/logo-blue.svg', - sameAs: [ - 'https://www.linkedin.com/company/klz-cables', - ], + sameAs: ['https://www.linkedin.com/company/klz-cables'], description: 'Premium Cable Solutions for Renewable Energy and Infrastructure.', address: { '@type': 'PostalAddress', @@ -36,15 +34,32 @@ export default function JsonLd({ id, data }: JsonLdProps) { }, 'query-input': 'required name=search_term_string', }, - } + }, ]; + // Harden schema items: Ensure each item is an object and has @context + const schemaData = (Array.isArray(rawData) ? rawData : [rawData]) + .filter((item): item is WithContext => { + // Basic sanity check: must be an object and not null + return typeof item === 'object' && item !== null; + }) + .map((item) => { + // Ensure @context is present and correctly typed for consumer libraries + if (!item['@context']) { + return { + ...item, + '@context': 'https://schema.org', + }; + } + return item; + }); + return (