skills
This commit is contained in:
parent
1a6dc01291
commit
a76bcb70e1
|
|
@ -4,18 +4,41 @@ import { getPublishedPostBySlug, getAuthorBySlug, getRelatedPosts, getPublishedP
|
|||
import { AnswerBox } from "@/components/aeo/AnswerBox";
|
||||
import { StepList } from "@/components/aeo/StepList";
|
||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||
import { SourcesList } from "@/components/aeo/SourcesList";
|
||||
import { AuthorCard } from "@/components/author/AuthorCard";
|
||||
import { RelatedPosts } from "@/components/blog/RelatedPosts";
|
||||
import { blogPostingSchema, howToSchema, faqPageSchema } from "@/lib/schema";
|
||||
import { blogPostingSchema, howToSchema, faqPageSchema, breadcrumbSchema } from "@/lib/schema";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const post = getPublishedPostBySlug(params.slug);
|
||||
if (!post) return {};
|
||||
|
||||
const ogImage = post.heroImage ? `https://www.qrmaster.net${post.heroImage}` : undefined;
|
||||
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
alternates: {
|
||||
canonical: `https://www.qrmaster.net/blog/${post.slug}`,
|
||||
},
|
||||
openGraph: {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
type: 'article',
|
||||
publishedTime: post.datePublished,
|
||||
modifiedTime: post.dateModified || post.datePublished,
|
||||
authors: ['https://www.qrmaster.net'],
|
||||
tags: post.keywords,
|
||||
images: ogImage ? [{ url: ogImage, alt: post.imageAlt || post.title }] : undefined,
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
images: ogImage ? [ogImage] : undefined,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +61,15 @@ export default function BlogPostPage({ params }: { params: { slug: string } }) {
|
|||
const howtoLd = post.keySteps?.length ? howToSchema(post, author) : null;
|
||||
const faqLd = post.faq ? faqPageSchema(post.faq) : null;
|
||||
|
||||
// Generate breadcrumb schema: Home → Learn → Pillar → Post
|
||||
const pillarName = post.pillar ? post.pillar.charAt(0).toUpperCase() + post.pillar.slice(1) : 'Blog';
|
||||
const breadcrumbLd = breadcrumbSchema([
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Learn', url: '/learn' },
|
||||
{ name: pillarName, url: `/learn/${post.pillar}` },
|
||||
{ name: post.title, url: `/blog/${post.slug}` },
|
||||
]);
|
||||
|
||||
return (
|
||||
<main className="container mx-auto max-w-4xl py-12 px-4">
|
||||
<Script id="ld-blogposting" type="application/ld+json" strategy="afterInteractive"
|
||||
|
|
@ -50,6 +82,8 @@ export default function BlogPostPage({ params }: { params: { slug: string } }) {
|
|||
<Script id="ld-faq" type="application/ld+json" strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqLd) }} />
|
||||
)}
|
||||
<Script id="ld-breadcrumb" type="application/ld+json" strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }} />
|
||||
|
||||
<header className="space-y-6 text-center max-w-3xl mx-auto mb-10">
|
||||
<div className="flex justify-center gap-2 text-sm text-gray-500 font-medium">
|
||||
|
|
@ -71,7 +105,12 @@ export default function BlogPostPage({ params }: { params: { slug: string } }) {
|
|||
)}
|
||||
<div className="text-left text-sm">
|
||||
<div className="font-bold text-gray-900">{author.name}</div>
|
||||
<div className="text-gray-500">Updated {post.updatedAt || post.date}</div>
|
||||
<div className="text-gray-500 text-xs mt-0.5">
|
||||
Published {post.date}
|
||||
{post.updatedAt && (
|
||||
<> <span className="mx-1">•</span> Updated {post.updatedAt}</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -97,6 +136,9 @@ export default function BlogPostPage({ params }: { params: { slug: string } }) {
|
|||
{/* AEO BLOCK: FAQ */}
|
||||
{!!post.faq?.length && <div className="mt-12"><FAQSection items={post.faq} /></div>}
|
||||
|
||||
{/* AEO BLOCK: SOURCES */}
|
||||
{!!post.sources?.length && <SourcesList sources={post.sources} />}
|
||||
|
||||
<div className="border-t border-gray-100 my-12"></div>
|
||||
|
||||
{author && <AuthorCard author={author} />}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import type { Source } from "@/lib/types";
|
||||
|
||||
type Props = {
|
||||
sources: Source[];
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export function SourcesList({ sources, title = "Sources & References" }: Props) {
|
||||
if (!sources?.length) return null;
|
||||
|
||||
return (
|
||||
<section className="rounded-xl border border-gray-100 bg-gray-50/50 p-6 my-8">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">{title}</h3>
|
||||
<ol className="space-y-2 list-decimal list-inside">
|
||||
{sources.map((source, index) => (
|
||||
<li key={index} className="text-sm text-gray-700">
|
||||
<cite className="not-italic">
|
||||
<a
|
||||
href={source.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:text-blue-800 hover:underline"
|
||||
>
|
||||
{source.name}
|
||||
</a>
|
||||
</cite>
|
||||
{source.accessDate && (
|
||||
<span className="text-gray-500 ml-2">
|
||||
(accessed {source.accessDate})
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -317,6 +317,12 @@ export const blogPosts: BlogPost[] = [
|
|||
{ question: "How do I connect QR analytics to Google Analytics?", answer: "Use UTMs and track landing page events (forms, purchases, calls) inside GA." },
|
||||
],
|
||||
relatedSlugs: ["qr-code-tracking-guide-2025", "utm-parameter-qr-codes", "qr-code-scan-statistics-2026", "trackable-qr-codes"],
|
||||
sources: [
|
||||
{ name: "Yahoo Finance: Global QR Code Payments Market Analysis 2025-2030", url: "https://finance.yahoo.com/news/analysis-global-qr-code-payments-155300360.html", accessDate: "January 2026" },
|
||||
{ name: "QRCodeChimp: QR Code Statistics for 2026", url: "https://www.qrcodechimp.com/qr-code-statistics/", accessDate: "January 2026" },
|
||||
{ name: "FBI IC3: Warning on QR Code Phishing Attacks", url: "https://www.ic3.gov/CSA/2026/260108.pdf", accessDate: "January 2026" },
|
||||
{ name: "Barracuda Networks: Email Threat Radar January 2026", url: "https://blog.barracuda.com/2026/01/22/email-threat-radar-january-2026", accessDate: "January 2026" },
|
||||
],
|
||||
content: `<div class="blog-content">
|
||||
<h2>What Are Scan Analytics?</h2>
|
||||
<p>Scan analytics provide comprehensive insights into how users interact with your <a href="/qr-code-tracking" class="text-primary-600 hover:underline">dynamic QR codes</a>. Our advanced dashboard tracks scan analytics including geographic location, device types, scan timestamps, and user engagement patterns.</p>
|
||||
|
|
@ -713,6 +719,11 @@ export const blogPosts: BlogPost[] = [
|
|||
],
|
||||
|
||||
relatedSlugs: ["qr-code-tracking-guide-2025", "qr-code-analytics", "dynamic-vs-static-qr-codes", "qr-code-small-business"],
|
||||
sources: [
|
||||
{ name: "Mordor Intelligence: QR Codes Market Size & Trend Analysis 2026-2031", url: "https://www.mordorintelligence.com/industry-reports/qr-codes-market", accessDate: "January 2026" },
|
||||
{ name: "Bitly: 30+ QR Code Statistics for 2026", url: "https://bitly.com/blog/qr-code-statistics/", accessDate: "January 2026" },
|
||||
{ name: "QR Code Tiger: QR Code Adoption Rate Stats 2026", url: "https://www.qrcode-tiger.com/qr-code-adoption-rate", accessDate: "January 2026" },
|
||||
],
|
||||
|
||||
content: `<div class="blog-content">
|
||||
<p>Most QR codes are “dumb.” They work, but you have zero idea what happens after people scan. That’s why <strong>trackable QR codes</strong> are a game changer: you can measure scans, compare placements, and optimize campaigns like a real marketer.</p>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,10 @@ export function organizationSchema() {
|
|||
height: 630,
|
||||
},
|
||||
sameAs: [
|
||||
'https://twitter.com/qrmaster',
|
||||
'https://www.wikidata.org/wiki/Q137918857',
|
||||
'https://x.com/TIMO_QRMASTER',
|
||||
'https://www.linkedin.com/in/qr-master-44b6863a2/',
|
||||
'https://www.instagram.com/qrmaster_net/',
|
||||
],
|
||||
contactPoint: [{
|
||||
'@type': 'ContactPoint',
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@ export type FAQItem = {
|
|||
answer: string; // allow HTML or plain
|
||||
};
|
||||
|
||||
export type Source = {
|
||||
name: string; // "Statista QR Code Market Report 2026"
|
||||
url: string; // "https://www.statista.com/..."
|
||||
accessDate?: string; // "January 2026"
|
||||
};
|
||||
|
||||
export type BlogPost = {
|
||||
|
||||
slug: string;
|
||||
title: string;
|
||||
excerpt: string; // kept for backward compatibility if needed, maps to description
|
||||
|
|
@ -34,6 +41,7 @@ export type BlogPost = {
|
|||
keySteps?: string[]; // plain
|
||||
faq?: FAQItem[];
|
||||
relatedSlugs?: string[];
|
||||
sources?: Source[]; // Primary sources for AEO trust signals
|
||||
|
||||
// Main content
|
||||
content: string; // HTML string (mapped from contentHtml in spec to content here to match existing usage if preferred, or we stick to contentHtml)
|
||||
|
|
|
|||
Loading…
Reference in New Issue