80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
import { blogPosts } from "./blog-data";
|
|
import { authors } from "./author-data";
|
|
import type { BlogPost, PillarKey, AuthorProfile } from "./types";
|
|
|
|
/**
|
|
* Blog slugs that are 301-redirected in next.config.mjs.
|
|
* They must not appear in the blog index, the learn pillars, the sitemap
|
|
* or IndexNow submissions - linking to a redirect source wastes crawl budget
|
|
* and passes link equity through an unnecessary hop.
|
|
* Keep this in sync with the redirects() block in next.config.mjs.
|
|
*/
|
|
export const REDIRECTED_BLOG_SLUGS = new Set<string>([
|
|
"qr-code-analytics",
|
|
"qr-code-restaurant-menu",
|
|
// Duplicate of the standalone /qr-code-print-size-guide, which is the version
|
|
// Google actually ranks. Both were live and self-canonical until 2026-08-13.
|
|
"qr-code-print-size-guide",
|
|
]);
|
|
|
|
export function isRedirectedSlug(slug: string): boolean {
|
|
return REDIRECTED_BLOG_SLUGS.has(slug);
|
|
}
|
|
|
|
export function getPublishedPosts(): BlogPost[] {
|
|
const currentDate = new Date();
|
|
return blogPosts.filter(p => {
|
|
if (!p.published) return false;
|
|
if (REDIRECTED_BLOG_SLUGS.has(p.slug)) return false;
|
|
const publishDate = p.datePublished ? new Date(p.datePublished) : new Date(p.date);
|
|
return publishDate <= currentDate;
|
|
});
|
|
}
|
|
|
|
export function getPostBySlug(slug: string): BlogPost | undefined {
|
|
return blogPosts.find(p => p.slug === slug);
|
|
}
|
|
|
|
export function getPublishedPostBySlug(slug: string): BlogPost | undefined {
|
|
const p = getPostBySlug(slug);
|
|
if (!p?.published) return undefined;
|
|
|
|
const currentDate = new Date();
|
|
const publishDate = p.datePublished ? new Date(p.datePublished) : new Date(p.date);
|
|
return publishDate <= currentDate ? p : undefined;
|
|
}
|
|
|
|
export function getPostsByPillar(pillar: PillarKey): BlogPost[] {
|
|
return getPublishedPosts()
|
|
.filter(p => p.pillar === pillar)
|
|
.sort((a, b) => (new Date(a.datePublished).getTime() < new Date(b.datePublished).getTime() ? 1 : -1));
|
|
}
|
|
|
|
export function getAuthorBySlug(slug: string): AuthorProfile | undefined {
|
|
return authors.find(a => a.slug === slug);
|
|
}
|
|
|
|
export function getPostsByAuthor(slug: string): BlogPost[] {
|
|
return getPublishedPosts()
|
|
.filter(p => p.authorSlug === slug)
|
|
.sort((a, b) => (new Date(a.datePublished).getTime() < new Date(b.datePublished).getTime() ? 1 : -1));
|
|
}
|
|
|
|
export function getRelatedPosts(post: BlogPost, limit = 4): BlogPost[] {
|
|
const published = getPublishedPosts();
|
|
|
|
// explicit relatedSlugs first
|
|
const explicit = (post.relatedSlugs ?? [])
|
|
.map(s => published.find(p => p.slug === s))
|
|
.filter((p): p is BlogPost => !!p);
|
|
|
|
if (explicit.length >= limit) return explicit.slice(0, limit);
|
|
|
|
// fallback: same pillar, not itself, newest
|
|
const fallback = published
|
|
.filter(p => p.slug !== post.slug && p.pillar === post.pillar)
|
|
.slice(0, limit - explicit.length);
|
|
|
|
return [...explicit, ...fallback];
|
|
}
|