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 { AnswerBox } from "@/components/aeo/AnswerBox";
|
||||||
import { StepList } from "@/components/aeo/StepList";
|
import { StepList } from "@/components/aeo/StepList";
|
||||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||||
|
import { SourcesList } from "@/components/aeo/SourcesList";
|
||||||
import { AuthorCard } from "@/components/author/AuthorCard";
|
import { AuthorCard } from "@/components/author/AuthorCard";
|
||||||
import { RelatedPosts } from "@/components/blog/RelatedPosts";
|
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 Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||||
const post = getPublishedPostBySlug(params.slug);
|
const post = getPublishedPostBySlug(params.slug);
|
||||||
if (!post) return {};
|
if (!post) return {};
|
||||||
|
|
||||||
|
const ogImage = post.heroImage ? `https://www.qrmaster.net${post.heroImage}` : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: post.title,
|
title: post.title,
|
||||||
description: post.description,
|
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 howtoLd = post.keySteps?.length ? howToSchema(post, author) : null;
|
||||||
const faqLd = post.faq ? faqPageSchema(post.faq) : 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 (
|
return (
|
||||||
<main className="container mx-auto max-w-4xl py-12 px-4">
|
<main className="container mx-auto max-w-4xl py-12 px-4">
|
||||||
<Script id="ld-blogposting" type="application/ld+json" strategy="afterInteractive"
|
<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"
|
<Script id="ld-faq" type="application/ld+json" strategy="afterInteractive"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqLd) }} />
|
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">
|
<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">
|
<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="text-left text-sm">
|
||||||
<div className="font-bold text-gray-900">{author.name}</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -97,6 +136,9 @@ export default function BlogPostPage({ params }: { params: { slug: string } }) {
|
||||||
{/* AEO BLOCK: FAQ */}
|
{/* AEO BLOCK: FAQ */}
|
||||||
{!!post.faq?.length && <div className="mt-12"><FAQSection items={post.faq} /></div>}
|
{!!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>
|
<div className="border-t border-gray-100 my-12"></div>
|
||||||
|
|
||||||
{author && <AuthorCard author={author} />}
|
{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." },
|
{ 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"],
|
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">
|
content: `<div class="blog-content">
|
||||||
<h2>What Are Scan Analytics?</h2>
|
<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>
|
<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"],
|
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">
|
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>
|
<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,
|
height: 630,
|
||||||
},
|
},
|
||||||
sameAs: [
|
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: [{
|
contactPoint: [{
|
||||||
'@type': 'ContactPoint',
|
'@type': 'ContactPoint',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,14 @@ export type FAQItem = {
|
||||||
answer: string; // allow HTML or plain
|
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 = {
|
export type BlogPost = {
|
||||||
|
|
||||||
slug: string;
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
excerpt: string; // kept for backward compatibility if needed, maps to description
|
excerpt: string; // kept for backward compatibility if needed, maps to description
|
||||||
|
|
@ -34,6 +41,7 @@ export type BlogPost = {
|
||||||
keySteps?: string[]; // plain
|
keySteps?: string[]; // plain
|
||||||
faq?: FAQItem[];
|
faq?: FAQItem[];
|
||||||
relatedSlugs?: string[];
|
relatedSlugs?: string[];
|
||||||
|
sources?: Source[]; // Primary sources for AEO trust signals
|
||||||
|
|
||||||
// Main content
|
// 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)
|
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