perf: memoize post queries and reduce JSON-LD bloat
This commit is contained in:
@@ -145,8 +145,6 @@ export default async function BlogPostPage({ params }: Props) {
|
|||||||
wordCount: wordCount,
|
wordCount: wordCount,
|
||||||
readingTime: `${readingTime} min read`,
|
readingTime: `${readingTime} min read`,
|
||||||
}),
|
}),
|
||||||
articleBody: textContent.slice(0, 5000),
|
|
||||||
inLanguage: siteConfig.defaultLocale,
|
|
||||||
url: postUrl,
|
url: postUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ export default async function RootLayout({
|
|||||||
<head>
|
<head>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||||
|
<link rel="font" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="font" href="https://fonts.gstatic.com" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<NextTopLoader
|
<NextTopLoader
|
||||||
|
|||||||
49
lib/posts.ts
49
lib/posts.ts
@@ -1,11 +1,18 @@
|
|||||||
import { allPosts, allPages, Post, Page } from 'contentlayer2/generated';
|
import { allPosts, allPages, Post, Page } from 'contentlayer2/generated';
|
||||||
|
|
||||||
|
let _sortedCache: Post[] | null = null;
|
||||||
|
let _relatedCache: Map<string, Post[]> = new Map();
|
||||||
|
let _neighborsCache: Map<string, { newer?: Post; older?: Post }> = new Map();
|
||||||
|
let _tagsCache: { tag: string; slug: string; count: number }[] | null = null;
|
||||||
|
|
||||||
export function getAllPostsSorted(): Post[] {
|
export function getAllPostsSorted(): Post[] {
|
||||||
return [...allPosts].sort((a, b) => {
|
if (_sortedCache) return _sortedCache;
|
||||||
|
_sortedCache = [...allPosts].sort((a, b) => {
|
||||||
const aDate = a.published_at ? new Date(a.published_at).getTime() : 0;
|
const aDate = a.published_at ? new Date(a.published_at).getTime() : 0;
|
||||||
const bDate = b.published_at ? new Date(b.published_at).getTime() : 0;
|
const bDate = b.published_at ? new Date(b.published_at).getTime() : 0;
|
||||||
return bDate - aDate;
|
return bDate - aDate;
|
||||||
});
|
});
|
||||||
|
return _sortedCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPostBySlug(slug: string): Post | undefined {
|
export function getPostBySlug(slug: string): Post | undefined {
|
||||||
@@ -38,24 +45,31 @@ export function getTagSlug(tag: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getAllTagsWithCount(): { tag: string; slug: string; count: number }[] {
|
export function getAllTagsWithCount(): { tag: string; slug: string; count: number }[] {
|
||||||
const map = new Map<string, number>();
|
if (_tagsCache) return _tagsCache;
|
||||||
|
|
||||||
|
const map = new Map<string, number>();
|
||||||
for (const post of allPosts) {
|
for (const post of allPosts) {
|
||||||
if (!post.tags) continue;
|
if (!post.tags) continue;
|
||||||
for (const tag of post.tags) {
|
for (const postTag of post.tags) {
|
||||||
map.set(tag, (map.get(tag) ?? 0) + 1);
|
map.set(postTag, (map.get(postTag) ?? 0) + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(map.entries())
|
_tagsCache = Array.from(map.entries())
|
||||||
.map(([tag, count]) => ({ tag, slug: getTagSlug(tag), count }))
|
.map(([tag, count]) => ({ tag, slug: getTagSlug(tag), count }))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (b.count === a.count) return a.tag.localeCompare(b.tag);
|
if (b.count === a.count) return a.tag.localeCompare(b.tag);
|
||||||
return b.count - a.count;
|
return b.count - a.count;
|
||||||
});
|
});
|
||||||
|
return _tagsCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRelatedPosts(target: Post, limit = 3): Post[] {
|
export function getRelatedPosts(target: Post, limit = 3): Post[] {
|
||||||
|
const cacheKey = `${target._id}-${limit}`;
|
||||||
|
if (_relatedCache.has(cacheKey)) {
|
||||||
|
return _relatedCache.get(cacheKey)!;
|
||||||
|
}
|
||||||
|
|
||||||
const targetTags = new Set(target.tags?.map((tag) => tag.toLowerCase()) ?? []);
|
const targetTags = new Set(target.tags?.map((tag) => tag.toLowerCase()) ?? []);
|
||||||
const candidates = getAllPostsSorted().filter((post) => post._id !== target._id);
|
const candidates = getAllPostsSorted().filter((post) => post._id !== target._id);
|
||||||
|
|
||||||
@@ -84,28 +98,39 @@ export function getRelatedPosts(target: Post, limit = 3): Post[] {
|
|||||||
.slice(0, limit)
|
.slice(0, limit)
|
||||||
.map((entry) => entry.post);
|
.map((entry) => entry.post);
|
||||||
|
|
||||||
|
let result: Post[];
|
||||||
if (scored.length >= limit) {
|
if (scored.length >= limit) {
|
||||||
return scored;
|
result = scored;
|
||||||
|
} else {
|
||||||
|
const fallback = candidates.filter(
|
||||||
|
(post) => !scored.some((existing) => existing._id === post._id)
|
||||||
|
);
|
||||||
|
result = [...scored, ...fallback.slice(0, limit - scored.length)].slice(0, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallback = candidates.filter(
|
_relatedCache.set(cacheKey, result);
|
||||||
(post) => !scored.some((existing) => existing._id === post._id)
|
return result;
|
||||||
);
|
|
||||||
|
|
||||||
return [...scored, ...fallback.slice(0, limit - scored.length)].slice(0, limit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPostNeighbors(target: Post): {
|
export function getPostNeighbors(target: Post): {
|
||||||
newer?: Post;
|
newer?: Post;
|
||||||
older?: Post;
|
older?: Post;
|
||||||
} {
|
} {
|
||||||
|
const cacheKey = target._id;
|
||||||
|
if (_neighborsCache.has(cacheKey)) {
|
||||||
|
return _neighborsCache.get(cacheKey)!;
|
||||||
|
}
|
||||||
|
|
||||||
const sorted = getAllPostsSorted();
|
const sorted = getAllPostsSorted();
|
||||||
const index = sorted.findIndex((post) => post._id === target._id);
|
const index = sorted.findIndex((post) => post._id === target._id);
|
||||||
|
|
||||||
if (index === -1) return {};
|
if (index === -1) return {};
|
||||||
|
|
||||||
return {
|
const result = {
|
||||||
newer: index > 0 ? sorted[index - 1] : undefined,
|
newer: index > 0 ? sorted[index - 1] : undefined,
|
||||||
older: index < sorted.length - 1 ? sorted[index + 1] : undefined
|
older: index < sorted.length - 1 ? sorted[index + 1] : undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_neighborsCache.set(cacheKey, result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/types/routes.d.ts";
|
import "./.next/dev/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
Reference in New Issue
Block a user