Skip to content

Commit dcdf007

Browse files
authored
chore(blog): improved ssr (#7640)
* chore(blog): improved ssr * chore(blog): feedback * chore(blog): further feedback * updates * updates from coderabbit * updates from coderabbit
1 parent 2203b3f commit dcdf007

9 files changed

Lines changed: 304 additions & 430 deletions

File tree

apps/blog/src/app/(blog)/layout.tsx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,38 @@ export function baseOptions() {
1212
sub: [
1313
{
1414
text: "Postgres",
15-
url: "/postgres",
15+
url: "https://www.prisma.io/postgres",
1616
desc: "Managed Postgres for global workloads",
1717
icon: "fa-regular fa-chart-pyramid",
1818
},
1919
{
2020
text: "ORM",
21-
url: "/orm",
21+
url: "https://www.prisma.io/orm",
2222
desc: "Managed Postgres for global workloads",
2323
icon: "fa-regular fa-database",
2424
},
2525
{
2626
text: "Studio",
2727
icon: "fa-regular fa-table",
28-
url: "/studio",
28+
url: "https://www.prisma.io/studio",
2929
desc: "Explore and manipulate your data",
3030
},
3131
{
3232
icon: "fa-regular fa-bolt",
3333
text: "Accelerate",
3434
desc: "Make your database global",
35-
url: "/accelerate",
35+
url: "https://www.prisma.io/accelerate",
3636
},
3737
{
3838
icon: "fa-regular fa-plug",
3939
text: "Management API",
4040
desc: "Offer Postgres to your users",
41-
url: "/",
41+
url: "https://www.prisma.io/management-api",
4242
},
4343
],
4444
},
4545
{
46-
url: "/pricing",
46+
url: "https://www.prisma.io/pricing",
4747
text: "Pricing",
4848
},
4949
{
@@ -52,42 +52,42 @@ export function baseOptions() {
5252
sub: [
5353
{
5454
text: "MCP",
55-
url: "/mcp",
55+
url: "https://www.prisma.io/mcp",
5656
icon: "fa-regular fa-message-code",
5757
},
5858
{
5959
text: "Get started",
60-
url: "/docs",
60+
url: "https://www.prisma.io/docs",
6161
icon: "fa-regular fa-book-open",
6262
},
6363
{
6464
text: "Tutorials",
65-
url: "/learn",
65+
url: "https://www.prisma.io/learn",
6666
icon: "fa-regular fa-clapperboard-play",
6767
},
6868
{
6969
text: "Examples",
70-
url: "/",
70+
url: "https://www.prisma.io/examples",
7171
icon: "fa-regular fa-grid-2",
7272
},
7373
{
7474
text: "Stack",
75-
url: "/stack",
75+
url: "https://www.prisma.io/stack",
7676
icon: "fa-regular fa-layer-group",
7777
},
7878
{
7979
text: "Ecosystem",
80-
url: "/ecosystem",
80+
url: "https://www.prisma.io/ecosystem",
8181
icon: "fa-regular fa-globe",
8282
},
8383
{
8484
text: "Customer stories",
85-
url: "/",
85+
url: "https://www.prisma.io/showcase",
8686
icon: "fa-regular fa-users",
8787
},
8888
{
8989
text: "Data guide",
90-
url: "/dataguide",
90+
url: "https://www.prisma.io/dataguide",
9191
icon: "fa-regular fa-file-binary",
9292
},
9393
],
@@ -97,7 +97,7 @@ export function baseOptions() {
9797
text: "Partners",
9898
},
9999
{
100-
url: "/blog",
100+
url: "https://www.prisma.io/blog",
101101
text: "Blog",
102102
},
103103
],
@@ -106,7 +106,7 @@ export function baseOptions() {
106106

107107
export default function Layout({ children }: { children: React.ReactNode }) {
108108
return (
109-
<ThemeProvider defaultTheme="system" storageKey="blog-theme">
109+
<ThemeProvider defaultTheme="system" storageKey="theme">
110110
<WebNavigation links={baseOptions().links} />
111111
{children}
112112
<Footer />

apps/blog/src/app/(blog)/page.tsx

Lines changed: 158 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,81 @@
1-
import { Suspense } from "react";
21
import { blog, getPageImage } from "@/lib/source";
3-
import { BlogGrid } from "@/components/BlogGrid";
2+
import { BlogGrid, type BlogCardItem } from "@/components/BlogGrid";
3+
import { CategoryTagFilter } from "@/components/CategoryTagFilter";
44
import { BLOG_HOME_DESCRIPTION, BLOG_HOME_TITLE } from "@/lib/blog-metadata";
55
import type { Metadata } from "next";
66
import { withBlogBasePath, withBlogBasePathForImageSrc } from "@/lib/url";
7+
import {
8+
Pagination,
9+
PaginationContent,
10+
PaginationEllipsis,
11+
PaginationItem,
12+
PaginationLink,
13+
PaginationNext,
14+
PaginationPrevious,
15+
} from "@prisma/eclipse";
16+
import { LargeSearchToggle } from "@/components/search-toggle";
17+
18+
const SHOW_ALL = "show-all";
19+
const PAGE_SIZE = 12;
20+
21+
type BlogHomeSearchParams = {
22+
page?: string | string[];
23+
tag?: string | string[];
24+
};
25+
26+
function getFirstQueryValue(value?: string | string[]): string | undefined {
27+
if (Array.isArray(value)) {
28+
return value[0];
29+
}
30+
31+
return value;
32+
}
33+
34+
function parsePage(value: string | undefined): number {
35+
const parsed = Number.parseInt(value ?? "1", 10);
36+
return Number.isNaN(parsed) || parsed < 1 ? 1 : parsed;
37+
}
38+
39+
function buildBlogHref(tag: string, page: number): string {
40+
const params = new URLSearchParams();
41+
42+
if (tag !== SHOW_ALL) {
43+
params.set("tag", tag);
44+
}
45+
46+
if (page > 1) {
47+
params.set("page", String(page));
48+
}
49+
50+
const query = params.toString();
51+
const basePath = "/"
52+
return query ? `${basePath}?${query}` : basePath;
53+
}
54+
55+
function getPaginationSequence(totalPages: number, currentPage: number) {
56+
if (totalPages <= 7) {
57+
return Array.from({ length: totalPages }, (_, index) => index + 1);
58+
}
59+
60+
const pages: Array<number | "ellipsis"> = [1];
61+
const start = Math.max(2, currentPage - 1);
62+
const end = Math.min(totalPages - 1, currentPage + 1);
63+
64+
if (start > 2) {
65+
pages.push("ellipsis");
66+
}
67+
68+
for (let page = start; page <= end; page += 1) {
69+
pages.push(page);
70+
}
71+
72+
if (end < totalPages - 1) {
73+
pages.push("ellipsis");
74+
}
75+
76+
pages.push(totalPages);
77+
return pages;
78+
}
779

880
export async function generateMetadata(): Promise<Metadata> {
981
return {
@@ -28,7 +100,12 @@ export async function generateMetadata(): Promise<Metadata> {
28100
};
29101
}
30102

31-
export default function BlogHome() {
103+
export default async function BlogHome({
104+
searchParams,
105+
}: {
106+
searchParams?: Promise<BlogHomeSearchParams> | BlogHomeSearchParams;
107+
}) {
108+
const resolvedSearchParams = (await Promise.resolve(searchParams)) ?? {};
32109
const posts = blog.getPages().sort((a, b) => {
33110
const aTime =
34111
a.data.date instanceof Date
@@ -48,7 +125,7 @@ export default function BlogHome() {
48125
};
49126

50127

51-
const items = posts.map((post) => {
128+
const items: BlogCardItem[] = posts.map((post) => {
52129
const data = post.data as any;
53130

54131
// Safely convert date to ISO string with validation
@@ -77,52 +154,93 @@ export default function BlogHome() {
77154
tags: data.tags,
78155
};
79156
});
157+
80158
const uniqueTags = [
81-
...new Set(items.filter((item) => item.tags).flatMap((item) => item.tags)),
159+
...new Set(
160+
items
161+
.flatMap((item) => item.tags ?? [])
162+
.filter((tag): tag is string => Boolean(tag)),
163+
),
82164
];
83-
165+
const validTags = new Set(uniqueTags);
166+
const tagFromQuery = getFirstQueryValue(resolvedSearchParams.tag);
167+
const currentCategory =
168+
tagFromQuery && validTags.has(tagFromQuery) ? tagFromQuery : SHOW_ALL;
169+
const filteredItems =
170+
currentCategory === SHOW_ALL
171+
? items
172+
: items.filter((item) => item.tags?.includes(currentCategory));
173+
174+
const totalPages = Math.max(1, Math.ceil(filteredItems.length / PAGE_SIZE));
175+
const pageFromQuery = parsePage(getFirstQueryValue(resolvedSearchParams.page));
176+
const currentPage = Math.max(1, Math.min(pageFromQuery, totalPages));
177+
178+
const shouldShowFeatured = currentCategory === SHOW_ALL && currentPage === 1;
179+
const featuredPost = shouldShowFeatured ? filteredItems[0] : undefined;
180+
const postsToRender = shouldShowFeatured
181+
? filteredItems.slice(1, PAGE_SIZE)
182+
: filteredItems.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE);
183+
184+
const paginationSequence = getPaginationSequence(totalPages, currentPage);
185+
84186
return (
85187
<main className="flex-1 w-full max-w-249 mx-auto px-4 py-8 z-1">
86188
<h1 className="stretch-display text-4xl font-bold mb-2 landing-h1 text-center mt-9 font-sans-display">
87189
Blog
88190
</h1>
89-
{/* Category pills (static "Show all" to match layout) */}
90191
<div className="pt-6 pb-12 mt-10">
91-
{/* Grid with pagination */}
92-
<Suspense
93-
fallback={
94-
<div className="animate-pulse">
95-
<div className="flex justify-between items-center gap-4 mb-8">
96-
<div className="flex flex-wrap gap-2">
97-
{[...Array(5)].map((_, index) => (
98-
<div
99-
key={`pill-${index}`}
100-
className="h-8 w-20 rounded-full bg-fd-secondary border border-fd-primary/20"
101-
/>
102-
))}
103-
</div>
104-
<div className="h-10 w-20 md:w-52 rounded-full bg-fd-secondary border border-fd-primary/20" />
105-
</div>
106-
107-
<div className="rounded-square border border-fd-primary/20 bg-fd-secondary h-64 md:h-80 mb-12" />
108-
109-
<div className="grid gap-6 mt-12 grid-cols-1">
110-
{items.slice(0, 6).map((post) => (
111-
<div
112-
key={post.url}
113-
className="h-44 border-b border-fd-primary/20 bg-fd-secondary/60"
114-
/>
115-
))}
116-
</div>
117-
</div>
118-
}
119-
>
120-
<BlogGrid
121-
items={items}
122-
pageSize={12}
192+
<div className="flex justify-between items-center gap-4 mb-8">
193+
<CategoryTagFilter
123194
uniqueTags={uniqueTags}
195+
currentCategory={currentCategory}
196+
className="flex justify-center flex-wrap gap-1"
124197
/>
125-
</Suspense>
198+
<LargeSearchToggle className="w-20 h-full md:w-52" />
199+
</div>
200+
201+
<BlogGrid
202+
items={postsToRender}
203+
featuredPost={featuredPost}
204+
currentCategory={currentCategory}
205+
/>
206+
207+
<div className="mt-8">
208+
{totalPages > 1 ? (
209+
<Pagination>
210+
<PaginationContent>
211+
<PaginationItem>
212+
<PaginationPrevious
213+
href={buildBlogHref(currentCategory, Math.max(1, currentPage - 1))}
214+
aria-disabled={currentPage === 1}
215+
/>
216+
</PaginationItem>
217+
{paginationSequence.map((entry, index) => (
218+
<PaginationItem key={`${entry}-${index}`}>
219+
{entry === "ellipsis" ? (
220+
<PaginationEllipsis />
221+
) : (
222+
<PaginationLink
223+
href={buildBlogHref(currentCategory, entry)}
224+
isActive={entry === currentPage}
225+
>
226+
{entry}
227+
</PaginationLink>
228+
)}
229+
</PaginationItem>
230+
))}
231+
<PaginationItem>
232+
<PaginationNext
233+
href={buildBlogHref(
234+
currentCategory,
235+
Math.min(totalPages, currentPage + 1),
236+
)}
237+
aria-disabled={currentPage === totalPages}
238+
/>
239+
</PaginationItem>
240+
</PaginationContent>
241+
</Pagination>
242+
) : null}
243+
</div>
126244
</div>
127245
</main>
128246
);

apps/blog/src/app/global.css

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@
4141

4242
.newsletter-bg {
4343
background: linear-gradient(
44-
59deg,
45-
color-mix(in srgb, var(--color-foreground-ppg) 10%, transparent) 6.53%,
46-
color-mix(in srgb, var(--color-background-default) 10%, transparent) 74.71%
47-
);
44+
59deg,
45+
color-mix(in srgb, var(--color-foreground-ppg) 30%, transparent) 6.53%,
46+
var(--color-background-default) 74.71%
47+
)
4848
}
49+

0 commit comments

Comments
 (0)