Skip to content

Commit a11730e

Browse files
Merge branch 'main' into perf/markdown-img-default-width
2 parents 237d782 + d17bbc7 commit a11730e

14 files changed

Lines changed: 229 additions & 120 deletions

src/components/BlogCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
formatAuthors,
66
formatPublishedDate,
77
getBlogLibraries,
8-
} from '~/utils/blog'
8+
} from '~/utils/blog-format'
99
import { getOptimizedImageUrl } from '~/utils/optimizedImage'
1010

1111
export type BlogCardPost = {

src/components/LibraryLayout.tsx

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { GithubIcon } from '~/components/icons/GithubIcon'
44
import { DiscordIcon } from '~/components/icons/DiscordIcon'
55
import { Link, useMatches, useParams } from '@tanstack/react-router'
66
import { useLocalStorage } from '~/utils/useLocalStorage'
7+
import { useMediaQuery } from '~/utils/useMediaQuery'
78
import { useClickOutside } from '~/hooks/useClickOutside'
89
import { last } from '~/utils/utils'
910
import type { ConfigSchema, MenuItem } from '~/utils/config'
@@ -424,26 +425,6 @@ function clampProgress(value: number) {
424425
return Math.min(Math.max(value, 0), 1)
425426
}
426427

427-
function useMediaQuery(query: string) {
428-
const [matches, setMatches] = React.useState(false)
429-
430-
React.useEffect(() => {
431-
const mediaQueryList = window.matchMedia(query)
432-
const updateMatches = () => {
433-
setMatches(mediaQueryList.matches)
434-
}
435-
436-
updateMatches()
437-
mediaQueryList.addEventListener('change', updateMatches)
438-
439-
return () => {
440-
mediaQueryList.removeEventListener('change', updateMatches)
441-
}
442-
}, [query])
443-
444-
return matches
445-
}
446-
447428
function areDocsPartnerSlotsEqual(
448429
left: DocsPartnerScrollSlot | undefined,
449430
right: DocsPartnerScrollSlot | undefined,
@@ -903,6 +884,7 @@ export function LibraryLayout({
903884
surface: 'docs_rail',
904885
})
905886
const shouldShowDocsPartnerSlot = useMediaQuery('(max-width: 767.98px)')
887+
const isDesktopViewport = useMediaQuery('(min-width: 768px)')
906888

907889
const groupInitialOpenState = React.useMemo(() => {
908890
return visibleMenuConfig.reduce<Record<string, boolean>>(
@@ -1431,7 +1413,7 @@ export function LibraryLayout({
14311413
partners={activePartners}
14321414
/>
14331415
<div className="hidden md:block border border-gray-500/20 rounded-l-lg overflow-hidden w-full">
1434-
<RecentPostsWidget />
1416+
<RecentPostsWidget enabled={isDesktopViewport} />
14351417
</div>
14361418
</RightRail>
14371419
)}

src/components/RecentPostsWidget.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Link } from '@tanstack/react-router'
22
import { useQuery } from '@tanstack/react-query'
33
import { fetchRecentPosts, type RecentPost } from '~/utils/blog.functions'
4-
import { formatPublishedDate } from '~/utils/blog'
4+
import { formatPublishedDate } from '~/utils/blog-format'
55

66
type RecentPostsWidgetProps = {
77
posts?: ReadonlyArray<RecentPost>
8+
/** Set to false to skip the client fetch when the widget is rendered but not visible (e.g. hidden below a CSS breakpoint). Ignored when `posts` is provided. */
9+
enabled?: boolean
810
}
911

1012
function RecentPostsList({ posts }: { posts: ReadonlyArray<RecentPost> }) {
@@ -63,11 +65,14 @@ function RecentPostsSkeleton() {
6365
)
6466
}
6567

66-
export function RecentPostsWidget({ posts }: RecentPostsWidgetProps) {
68+
export function RecentPostsWidget({
69+
posts,
70+
enabled = true,
71+
}: RecentPostsWidgetProps) {
6772
const recentPostsQuery = useQuery({
6873
queryKey: ['recentPosts'],
6974
queryFn: () => fetchRecentPosts(),
70-
enabled: posts === undefined,
75+
enabled: posts === undefined && enabled,
7176
staleTime: 1000 * 60 * 5,
7277
})
7378

src/components/home/HomeSocialProofSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ArrowRight } from 'lucide-react'
55
import { Card } from '~/components/Card'
66
import { PartnersGrid } from '~/components/PartnersGrid'
77
import { Button } from '~/ui'
8-
import { formatAuthors, formatPublishedDate } from '~/utils/blog'
8+
import { formatAuthors, formatPublishedDate } from '~/utils/blog-format'
99
import type { RecentPost } from '~/utils/blog.functions'
1010

1111
type HomeSocialProofSectionProps = {

src/components/stack/CategoryArticle.tsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import {
2222
} from 'lucide-react'
2323

2424
import { LibraryWordmark } from '~/components/LibraryWordmark'
25-
import type { LibrarySlim } from '~/libraries'
26-
import { formatPublishedDate, getPostsForLibrary } from '~/utils/blog'
25+
import type { LibraryId, LibrarySlim } from '~/libraries'
26+
import { formatPublishedDate } from '~/utils/blog-format'
27+
import type { RelatedPost as RelatedPostData } from '~/utils/blog.functions'
2728
import {
2829
categoryMeta,
2930
getCategoryLibraries,
@@ -44,10 +45,16 @@ const libraryLinkClassName =
4445
const staticPanelClassName =
4546
'rounded-lg border border-zinc-200 bg-white p-4 dark:border-zinc-800 dark:bg-zinc-950'
4647

47-
export function CategoryArticle({ slug }: { slug: CategorySlug }) {
48+
export function CategoryArticle({
49+
slug,
50+
relatedPosts: relatedPostsData,
51+
}: {
52+
slug: CategorySlug
53+
relatedPosts: Array<RelatedPostData>
54+
}) {
4855
const meta = categoryMeta[slug]
4956
const libraries = getCategoryLibraries(slug)
50-
const relatedPosts = getRelatedPosts(libraries)
57+
const relatedPosts = reconstructRelatedPosts(libraries, relatedPostsData)
5158

5259
return (
5360
<div className="bg-white text-zinc-950 dark:bg-zinc-950 dark:text-zinc-50">
@@ -75,10 +82,23 @@ export function CategoryArticle({ slug }: { slug: CategorySlug }) {
7582
)
7683
}
7784

78-
function getRelatedPosts(libraries: Array<LibrarySlim>) {
79-
return libraries
80-
.flatMap((lib) => getPostsForLibrary(lib.id).map((post) => ({ post, lib })))
81-
.slice(0, 4)
85+
/**
86+
* Reconstructs {post, lib} pairs from the server-provided, already-ordered
87+
* and already-sliced related-posts data, using the in-memory `libraries`
88+
* array (pure, client-safe) rather than sending non-serializable LibrarySlim
89+
* objects (e.g. `handleRedirects`) over the server-fn RPC boundary.
90+
*/
91+
function reconstructRelatedPosts(
92+
libraries: Array<LibrarySlim>,
93+
data: Array<RelatedPostData>,
94+
): Array<RelatedPost> {
95+
const libraryById = new Map<LibraryId, LibrarySlim>(
96+
libraries.map((lib) => [lib.id, lib]),
97+
)
98+
return data.flatMap(({ libraryId, post }) => {
99+
const lib = libraryById.get(libraryId)
100+
return lib ? [{ post, lib }] : []
101+
})
82102
}
83103

84104
function Breadcrumb({ categoryName }: { categoryName: string }) {

src/routes/_library/$libraryId/$version.docs.blog.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ import { DocTitle } from '~/components/DocTitle'
77
import { BlogCard } from '~/components/BlogCard'
88
import { BlogAuthorFilter } from '~/components/BlogAuthorFilter'
99
import { getLibrary, type LibraryId } from '~/libraries'
10-
import { getDistinctAuthors, getPostsForLibrary } from '~/utils/blog'
10+
import { fetchPostsForLibrary } from '~/utils/blog.functions'
11+
import { getDistinctAuthors } from '~/utils/blog-format'
1112

1213
const searchSchema = v.object({
1314
author: v.fallback(v.optional(v.string()), undefined),
1415
})
1516

1617
export const Route = createFileRoute('/_library/$libraryId/$version/docs/blog')(
1718
{
19+
staleTime: Infinity,
1820
validateSearch: searchSchema,
21+
loader: ({ params }) => fetchPostsForLibrary({ data: params.libraryId }),
1922
component: RouteComponent,
2023
},
2124
)
@@ -26,7 +29,7 @@ function RouteComponent() {
2629
const navigate = Route.useNavigate()
2730
const library = getLibrary(libraryId as LibraryId)
2831

29-
const posts = getPostsForLibrary(libraryId as LibraryId)
32+
const posts = Route.useLoaderData()
3033
const authors = getDistinctAuthors(posts)
3134

3235
const filteredPosts = author

src/routes/blog.$.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { createFileRoute } from '@tanstack/react-router'
22
import { seo } from '~/utils/seo'
33
import { PostNotFound } from './blog'
4-
import { formatAuthors } from '~/utils/blog'
4+
import { formatAuthors } from '~/utils/blog-format'
55
import * as React from 'react'
66
import { MarkdownContent } from '~/components/markdown'
77
import { Card } from '~/components/Card'
88
import { LibrariesWidget } from '~/components/LibrariesWidget'
99
import { partners } from '~/utils/partners'
1010
import { PartnersRail, RightRail } from '~/components/RightRail'
1111
import { RecentPostsWidget } from '~/components/RecentPostsWidget'
12+
import { useMediaQuery } from '~/utils/useMediaQuery'
1213

1314
import { Toc } from '~/components/Toc'
1415
import { Breadcrumbs } from '~/components/Breadcrumbs'
@@ -76,6 +77,7 @@ function BlogPost() {
7677
const headings = markdown.headings
7778

7879
const isTocVisible = headings.length > 1
80+
const isDesktopViewport = useMediaQuery('(min-width: 768px)')
7981

8082
const markdownContainerRef = React.useRef<HTMLDivElement>(null)
8183
const [activeHeadings, setActiveHeadings] = React.useState<Array<string>>([])
@@ -204,7 +206,7 @@ function BlogPost() {
204206
partners={activePartners}
205207
/>
206208
<div className="hidden md:block border border-gray-500/20 rounded-l-lg overflow-hidden w-full">
207-
<RecentPostsWidget />
209+
<RecentPostsWidget enabled={isDesktopViewport} />
208210
</div>
209211
<Card>
210212
<LibrariesWidget />

src/routes/blog.index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { Link, createFileRoute } from '@tanstack/react-router'
22
import * as v from 'valibot'
33
import { BlogCard, type BlogCardPost } from '~/components/BlogCard'
44
import { BlogAuthorFilter } from '~/components/BlogAuthorFilter'
5-
import { getDistinctAuthors, getPublishedPosts } from '~/utils/blog'
5+
import { getPublishedPosts } from '~/utils/blog'
6+
import { getDistinctAuthors } from '~/utils/blog-format'
67

78
import { Footer } from '~/components/Footer'
89
import { PostNotFound } from './blog'

src/routes/rss[.]xml.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { createFileRoute } from '@tanstack/react-router'
22
import { setResponseHeader } from '@tanstack/react-start/server'
3-
import {
4-
getPublishedPosts,
5-
formatAuthors,
6-
publishedDateToUTCString,
7-
} from '~/utils/blog'
3+
import { getPublishedPosts } from '~/utils/blog'
4+
import { formatAuthors, publishedDateToUTCString } from '~/utils/blog-format'
85

96
function escapeXml(unsafe: string): string {
107
return unsafe

src/routes/stack.$category.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,33 @@ import { CategoryArticle } from '~/components/stack/CategoryArticle'
44
import {
55
categoryMeta,
66
categorySlugs,
7+
getCategoryLibraries,
78
type CategorySlug,
89
} from '~/components/stack/stack-categories'
10+
import { fetchRelatedPostsForLibraries } from '~/utils/blog.functions'
911
import { seo } from '~/utils/seo'
1012

1113
function isCategorySlug(value: string): value is CategorySlug {
1214
return (categorySlugs as readonly string[]).includes(value)
1315
}
1416

1517
export const Route = createFileRoute('/stack/$category')({
16-
loader: ({ params }) => {
18+
staleTime: Infinity,
19+
loader: async ({ params }) => {
1720
if (!isCategorySlug(params.category)) {
1821
throw notFound()
1922
}
20-
return { category: params.category, meta: categoryMeta[params.category] }
23+
24+
const libraries = getCategoryLibraries(params.category)
25+
const relatedPosts = await fetchRelatedPostsForLibraries({
26+
data: libraries.map((lib) => lib.id),
27+
})
28+
29+
return {
30+
category: params.category,
31+
meta: categoryMeta[params.category],
32+
relatedPosts,
33+
}
2134
},
2235
head: ({ loaderData }) => ({
2336
meta: seo({
@@ -31,6 +44,6 @@ export const Route = createFileRoute('/stack/$category')({
3144
})
3245

3346
function StackCategoryPage() {
34-
const { category } = Route.useLoaderData()
35-
return <CategoryArticle slug={category} />
47+
const { category, relatedPosts } = Route.useLoaderData()
48+
return <CategoryArticle slug={category} relatedPosts={relatedPosts} />
3649
}

0 commit comments

Comments
 (0)