Skip to content

Commit 3d08d13

Browse files
committed
fix: tighten landing page loading and oss stats caching
1 parent c09fe03 commit 3d08d13

16 files changed

Lines changed: 657 additions & 282 deletions

media/brand.sketch

171 KB
Binary file not shown.

src/components/LazySponsorSection.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
import React from 'react'
2-
import { useSuspenseQuery } from '@tanstack/react-query'
2+
import { useQuery } from '@tanstack/react-query'
33
import { ArrowRight } from 'lucide-react'
44
import { useIntersectionObserver } from '~/hooks/useIntersectionObserver'
55
import { getSponsorsForSponsorPack } from '~/server/sponsors'
66
import { Button } from '~/ui'
7-
import SponsorPack from './SponsorPack'
87
import PlaceholderSponsorPack from './PlaceholderSponsorPack'
98

9+
const LazySponsorPack = React.lazy(() => import('./SponsorPack'))
10+
1011
type LazySponsorSectionProps = {
1112
title?: React.ReactNode
1213
aspectRatio?: string
1314
showCTA?: boolean
1415
}
1516

1617
function SponsorPackWithQuery() {
17-
const { data: sponsors } = useSuspenseQuery({
18+
const { data: sponsors } = useQuery({
1819
queryKey: ['sponsors'],
1920
queryFn: () => getSponsorsForSponsorPack(),
2021
staleTime: 5 * 60 * 1000, // 5 minutes
2122
})
2223

23-
return <SponsorPack sponsors={sponsors} />
24+
if (!sponsors) {
25+
return <PlaceholderSponsorPack />
26+
}
27+
28+
return <LazySponsorPack sponsors={sponsors} />
2429
}
2530

2631
export function LazySponsorSection({

src/components/Navbar.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,14 @@ export function Navbar({ children }: { children: React.ReactNode }) {
225225
const loginEl = (
226226
<Link
227227
to="/login"
228-
className="flex items-center gap-1 rounded-md px-2 py-1.5
229-
bg-black dark:bg-white text-white dark:text-black
230-
hover:bg-gray-800 dark:hover:bg-gray-200
231-
transition-colors duration-200 text-xs font-medium"
228+
aria-label="Log In"
229+
className="flex shrink-0 items-center gap-1 rounded-md px-2 py-1.5 whitespace-nowrap
230+
bg-black dark:bg-white text-white dark:text-black
231+
hover:bg-gray-800 dark:hover:bg-gray-200
232+
transition-colors duration-200 text-xs font-medium"
232233
>
233234
<User className="w-3.5 h-3.5" />
234-
<span>Log In</span>
235+
<span className="hidden min-[430px]:inline">Log In</span>
235236
</Link>
236237
)
237238

@@ -347,7 +348,7 @@ export function Navbar({ children }: { children: React.ReactNode }) {
347348
<span>TanStack Intent</span>
348349
</Link>
349350
</div>
350-
<div className="flex items-center gap-2">
351+
<div className="flex items-center gap-1.5 sm:gap-2">
351352
<div className="hidden min-[750px]:block">{socialLinks}</div>
352353
<div className="hidden sm:block">
353354
<SearchButton />

src/components/SearchModal.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,9 @@ export function SearchModal() {
833833
ref={containerRef}
834834
onKeyDown={handleKeyDown}
835835
>
836+
<DialogPrimitive.Title className="sr-only">
837+
Search TanStack docs
838+
</DialogPrimitive.Title>
836839
<InstantSearch searchClient={searchClient} indexName="tanstack-test">
837840
<SearchFiltersProvider>
838841
<DynamicFilters />

src/components/builder/ExplorerPanel.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,9 +669,17 @@ export function ExplorerPanel() {
669669
const isSelected = selectedAddon === featureId
670670

671671
return (
672-
<button
672+
<div
673673
key={featureId}
674674
onClick={() => setSelectedAddon(featureId)}
675+
onKeyDown={(e) => {
676+
if (e.key === 'Enter' || e.key === ' ') {
677+
e.preventDefault()
678+
setSelectedAddon(featureId)
679+
}
680+
}}
681+
role="button"
682+
tabIndex={0}
675683
className={twMerge(
676684
'w-full flex items-center gap-2 px-3 py-2 rounded-lg text-left transition-colors',
677685
isSelected
@@ -720,7 +728,7 @@ export function ExplorerPanel() {
720728
</svg>
721729
</button>
722730
</div>
723-
</button>
731+
</div>
724732
)
725733
}
726734

src/db/schema.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,61 @@ export type NewNpmLibraryStatsCache = InferInsertModel<
368368
typeof npmLibraryStatsCache
369369
>
370370

371+
export const ossStatsCache = pgTable(
372+
'oss_stats_cache',
373+
{
374+
id: uuid('id').primaryKey().defaultRandom(),
375+
scopeType: varchar('scope_type', { length: 20 }).notNull(),
376+
scopeKey: varchar('scope_key', { length: 255 }).notNull(),
377+
378+
githubStarCount: integer('github_star_count').notNull().default(0),
379+
githubContributorCount: integer('github_contributor_count')
380+
.notNull()
381+
.default(0),
382+
githubDependentCount: integer('github_dependent_count'),
383+
githubForkCount: integer('github_fork_count'),
384+
githubRepositoryCount: integer('github_repository_count'),
385+
386+
githubDeltaStarCount: integer('github_delta_star_count'),
387+
githubDeltaContributorCount: integer('github_delta_contributor_count'),
388+
githubDeltaDependentCount: integer('github_delta_dependent_count'),
389+
githubDeltaForkCount: integer('github_delta_fork_count'),
390+
githubUpdatedAt: timestamp('github_updated_at', {
391+
withTimezone: true,
392+
mode: 'date',
393+
}),
394+
395+
npmTotalDownloads: bigint('npm_total_downloads', { mode: 'number' })
396+
.notNull()
397+
.default(0),
398+
npmRatePerDay: real('npm_rate_per_day'),
399+
npmPackageCount: integer('npm_package_count').notNull().default(0),
400+
npmUpdatedAt: timestamp('npm_updated_at', {
401+
withTimezone: true,
402+
mode: 'date',
403+
}),
404+
405+
timeDeltaMs: bigint('time_delta_ms', { mode: 'number' }),
406+
createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' })
407+
.notNull()
408+
.defaultNow(),
409+
updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'date' })
410+
.notNull()
411+
.defaultNow(),
412+
},
413+
(table) => ({
414+
scopeUnique: uniqueIndex('oss_stats_cache_scope_unique').on(
415+
table.scopeType,
416+
table.scopeKey,
417+
),
418+
scopeTypeIdx: index('oss_stats_cache_scope_type_idx').on(table.scopeType),
419+
scopeKeyIdx: index('oss_stats_cache_scope_key_idx').on(table.scopeKey),
420+
}),
421+
)
422+
423+
export type OssStatsCache = InferSelectModel<typeof ossStatsCache>
424+
export type NewOssStatsCache = InferInsertModel<typeof ossStatsCache>
425+
371426
// NPM Download Chunks cache table (for caching historical date range downloads)
372427
// This table stores immutable historical chunks and cacheable recent chunks
373428
// to avoid repeated API calls and rate limiting

src/routes/$libraryId/$version.index.tsx

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
useMatch,
3-
redirect,
43
Link,
54
notFound,
65
createFileRoute,
@@ -12,43 +11,7 @@ import { seo } from '~/utils/seo'
1211

1312
import { Button } from '~/ui'
1413
import { ConfigSchema } from '~/utils/config'
15-
import type { ComponentType } from 'react'
16-
17-
import QueryLanding from '~/components/landing/QueryLanding'
18-
import RouterLanding from '~/components/landing/RouterLanding'
19-
import TableLanding from '~/components/landing/TableLanding'
20-
import FormLanding from '~/components/landing/FormLanding'
21-
import StartLanding from '~/components/landing/StartLanding'
22-
import StoreLanding from '~/components/landing/StoreLanding'
23-
import VirtualLanding from '~/components/landing/VirtualLanding'
24-
import RangerLanding from '~/components/landing/RangerLanding'
25-
import PacerLanding from '~/components/landing/PacerLanding'
26-
import HotkeysLanding from '~/components/landing/HotkeysLanding'
27-
import ConfigLanding from '~/components/landing/ConfigLanding'
28-
import DbLanding from '~/components/landing/DbLanding'
29-
import AiLanding from '~/components/landing/AiLanding'
30-
import DevtoolsLanding from '~/components/landing/DevtoolsLanding'
31-
import CliLanding from '~/components/landing/CliLanding'
32-
import IntentLanding from '~/components/landing/IntentLanding'
33-
34-
const landingComponents: Partial<Record<LibraryId, ComponentType>> = {
35-
query: QueryLanding,
36-
router: RouterLanding,
37-
table: TableLanding,
38-
form: FormLanding,
39-
start: StartLanding,
40-
store: StoreLanding,
41-
virtual: VirtualLanding,
42-
ranger: RangerLanding,
43-
pacer: PacerLanding,
44-
hotkeys: HotkeysLanding,
45-
config: ConfigLanding,
46-
db: DbLanding,
47-
ai: AiLanding,
48-
devtools: DevtoolsLanding,
49-
cli: CliLanding,
50-
intent: IntentLanding,
51-
}
14+
import { landingComponents } from './$version'
5215

5316
export const Route = createFileRoute('/$libraryId/$version/')({
5417
head: (ctx) => {
@@ -67,17 +30,6 @@ export const Route = createFileRoute('/$libraryId/$version/')({
6730
}),
6831
}
6932
},
70-
beforeLoad: ({ params }) => {
71-
const { libraryId, version } = params
72-
// Libraries without landing pages redirect directly to docs
73-
if (!landingComponents[libraryId as LibraryId]) {
74-
throw redirect({
75-
to: '/$libraryId/$version/docs',
76-
params: { libraryId, version } as never,
77-
})
78-
}
79-
return undefined as never
80-
},
8133
// Stats load via Suspense in OpenSourceStats — no need to block the route loader
8234
component: LibraryVersionIndex,
8335
})

src/routes/$libraryId/$version.tsx

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,52 @@ import {
33
redirect,
44
notFound,
55
createFileRoute,
6+
lazyRouteComponent,
67
} from '@tanstack/react-router'
8+
import type { AsyncRouteComponent } from '@tanstack/react-router'
79
import { RedirectVersionBanner } from '~/components/RedirectVersionBanner'
810
import { findLibrary, getBranch } from '~/libraries'
11+
import type { LibraryId } from '~/libraries'
912
import { getTanstackDocsConfig } from '~/utils/config'
1013

14+
export const landingComponents: Partial<
15+
Record<LibraryId, AsyncRouteComponent<object>>
16+
> = {
17+
query: lazyRouteComponent(() => import('~/components/landing/QueryLanding')),
18+
router: lazyRouteComponent(
19+
() => import('~/components/landing/RouterLanding'),
20+
),
21+
table: lazyRouteComponent(() => import('~/components/landing/TableLanding')),
22+
form: lazyRouteComponent(() => import('~/components/landing/FormLanding')),
23+
start: lazyRouteComponent(() => import('~/components/landing/StartLanding')),
24+
store: lazyRouteComponent(() => import('~/components/landing/StoreLanding')),
25+
virtual: lazyRouteComponent(
26+
() => import('~/components/landing/VirtualLanding'),
27+
),
28+
ranger: lazyRouteComponent(
29+
() => import('~/components/landing/RangerLanding'),
30+
),
31+
pacer: lazyRouteComponent(() => import('~/components/landing/PacerLanding')),
32+
hotkeys: lazyRouteComponent(
33+
() => import('~/components/landing/HotkeysLanding'),
34+
),
35+
config: lazyRouteComponent(
36+
() => import('~/components/landing/ConfigLanding'),
37+
),
38+
db: lazyRouteComponent(() => import('~/components/landing/DbLanding')),
39+
ai: lazyRouteComponent(() => import('~/components/landing/AiLanding')),
40+
devtools: lazyRouteComponent(
41+
() => import('~/components/landing/DevtoolsLanding'),
42+
),
43+
cli: lazyRouteComponent(() => import('~/components/landing/CliLanding')),
44+
intent: lazyRouteComponent(
45+
() => import('~/components/landing/IntentLanding'),
46+
),
47+
}
48+
1149
export const Route = createFileRoute('/$libraryId/$version')({
1250
staleTime: 1000 * 60 * 5,
13-
beforeLoad: (ctx) => {
51+
beforeLoad: async (ctx) => {
1452
const { libraryId, version } = ctx.params
1553
const library = findLibrary(libraryId)
1654

@@ -25,6 +63,8 @@ export const Route = createFileRoute('/$libraryId/$version')({
2563
params: { libraryId, version: 'latest' } as never,
2664
})
2765
}
66+
67+
await landingComponents[libraryId as LibraryId]?.preload?.()
2868
},
2969
loader: async (ctx) => {
3070
const { libraryId, version } = ctx.params

0 commit comments

Comments
 (0)