refactor: render repo stats directly in ssr if cache is available

This commit is contained in:
Nicolas Meienberger 2026-02-28 10:17:55 +01:00
parent 66076e5841
commit 73c0511167
5 changed files with 21 additions and 8 deletions

View file

@ -3,9 +3,11 @@ import { Archive } from "lucide-react";
import { getRepositoryStatsOptions } from "~/client/api-client/@tanstack/react-query.gen"; import { getRepositoryStatsOptions } from "~/client/api-client/@tanstack/react-query.gen";
import { ByteSize } from "~/client/components/bytes-size"; import { ByteSize } from "~/client/components/bytes-size";
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
import type { GetRepositoryStatsResponse } from "~/client/api-client/types.gen";
type Props = { type Props = {
repositoryShortId: string; repositoryShortId: string;
initialStats?: GetRepositoryStatsResponse;
}; };
const toSafeNumber = (value: number | undefined) => { const toSafeNumber = (value: number | undefined) => {
@ -16,14 +18,15 @@ const toSafeNumber = (value: number | undefined) => {
return Math.max(0, value); return Math.max(0, value);
}; };
export function CompressionStatsChart({ repositoryShortId }: Props) { export function CompressionStatsChart({ repositoryShortId, initialStats }: Props) {
const { const {
data: stats, data: stats,
isLoading, isPending,
error, error,
} = useQuery({ } = useQuery({
...getRepositoryStatsOptions({ path: { shortId: repositoryShortId } }), ...getRepositoryStatsOptions({ path: { shortId: repositoryShortId } }),
retry: false, retry: false,
initialData: initialStats,
}); });
const storedSize = toSafeNumber(stats?.total_size); const storedSize = toSafeNumber(stats?.total_size);
@ -40,7 +43,7 @@ export function CompressionStatsChart({ repositoryShortId }: Props) {
const hasStats = !!stats && (storedSize > 0 || uncompressedSize > 0 || snapshotsCount > 0); const hasStats = !!stats && (storedSize > 0 || uncompressedSize > 0 || snapshotsCount > 0);
if (isLoading) { if (isPending) {
return ( return (
<Card className="p-6"> <Card className="p-6">
<p className="text-sm text-muted-foreground">Loading compression statistics...</p> <p className="text-sm text-muted-foreground">Loading compression statistics...</p>

View file

@ -6,15 +6,18 @@ import { RepositoryInfoTabContent } from "../tabs/info";
import { RepositorySnapshotsTabContent } from "../tabs/snapshots"; import { RepositorySnapshotsTabContent } from "../tabs/snapshots";
import { useNavigate, useSearch } from "@tanstack/react-router"; import { useNavigate, useSearch } from "@tanstack/react-router";
import type { BackupSchedule, Snapshot } from "~/client/lib/types"; import type { BackupSchedule, Snapshot } from "~/client/lib/types";
import type { GetRepositoryStatsResponse } from "~/client/api-client/types.gen";
export default function RepositoryDetailsPage({ export default function RepositoryDetailsPage({
repositoryId, repositoryId,
initialSnapshots, initialSnapshots,
initialBackupSchedules, initialBackupSchedules,
initialStats,
}: { }: {
repositoryId: string; repositoryId: string;
initialSnapshots?: Snapshot[]; initialSnapshots?: Snapshot[];
initialBackupSchedules?: BackupSchedule[]; initialBackupSchedules?: BackupSchedule[];
initialStats?: GetRepositoryStatsResponse;
}) { }) {
const navigate = useNavigate(); const navigate = useNavigate();
const { tab } = useSearch({ from: "/(dashboard)/repositories/$repositoryId/" }); const { tab } = useSearch({ from: "/(dashboard)/repositories/$repositoryId/" });
@ -32,7 +35,7 @@ export default function RepositoryDetailsPage({
<TabsTrigger value="snapshots">Snapshots</TabsTrigger> <TabsTrigger value="snapshots">Snapshots</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="info"> <TabsContent value="info">
<RepositoryInfoTabContent repository={data} /> <RepositoryInfoTabContent repository={data} initialStats={initialStats} />
</TabsContent> </TabsContent>
<TabsContent value="snapshots"> <TabsContent value="snapshots">
<Suspense> <Suspense>

View file

@ -14,6 +14,7 @@ import {
AlertDialogTitle, AlertDialogTitle,
} from "~/client/components/ui/alert-dialog"; } from "~/client/components/ui/alert-dialog";
import type { Repository } from "~/client/lib/types"; import type { Repository } from "~/client/lib/types";
import type { GetRepositoryStatsResponse } from "~/client/api-client/types.gen";
import { formatDateTime, formatTimeAgo } from "~/client/lib/datetime"; import { formatDateTime, formatTimeAgo } from "~/client/lib/datetime";
import { import {
cancelDoctorMutation, cancelDoctorMutation,
@ -30,6 +31,7 @@ import { cn } from "~/client/lib/utils";
type Props = { type Props = {
repository: Repository; repository: Repository;
initialStats?: GetRepositoryStatsResponse;
}; };
const getEffectiveLocalPath = (repository: Repository): string | null => { const getEffectiveLocalPath = (repository: Repository): string | null => {
@ -37,7 +39,7 @@ const getEffectiveLocalPath = (repository: Repository): string | null => {
return repository.config.path; return repository.config.path;
}; };
export const RepositoryInfoTabContent = ({ repository }: Props) => { export const RepositoryInfoTabContent = ({ repository, initialStats }: Props) => {
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
@ -232,7 +234,7 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
<DoctorReport repositoryStatus={repository.status} result={repository.doctorResult} /> <DoctorReport repositoryStatus={repository.status} result={repository.doctorResult} />
</Card> </Card>
<CompressionStatsChart repositoryShortId={repository.shortId} /> <CompressionStatsChart repositoryShortId={repository.shortId} initialStats={initialStats} />
</div> </div>
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}> <AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>

View file

@ -32,7 +32,7 @@ export function getRouter() {
routeTree, routeTree,
context: { queryClient }, context: { queryClient },
defaultPreload: "intent", defaultPreload: "intent",
scrollRestoration: true, scrollRestoration: false,
}); });
setupRouterSsrQueryIntegration({ setupRouterSsrQueryIntegration({
router, router,

View file

@ -2,6 +2,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { type } from "arktype"; import { type } from "arktype";
import { import {
getRepositoryOptions, getRepositoryOptions,
getRepositoryStatsOptions,
listBackupSchedulesOptions, listBackupSchedulesOptions,
listSnapshotsOptions, listSnapshotsOptions,
} from "~/client/api-client/@tanstack/react-query.gen"; } from "~/client/api-client/@tanstack/react-query.gen";
@ -14,17 +15,20 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/")
loader: async ({ params, context }) => { loader: async ({ params, context }) => {
const snapshotOptions = listSnapshotsOptions({ path: { shortId: params.repositoryId } }); const snapshotOptions = listSnapshotsOptions({ path: { shortId: params.repositoryId } });
const schedulesOptions = listBackupSchedulesOptions(); const schedulesOptions = listBackupSchedulesOptions();
const statsOptions = getRepositoryStatsOptions({ path: { shortId: params.repositoryId } });
const [res] = await Promise.all([ const [res] = await Promise.all([
context.queryClient.ensureQueryData(getRepositoryOptions({ path: { shortId: params.repositoryId } })), context.queryClient.ensureQueryData(getRepositoryOptions({ path: { shortId: params.repositoryId } })),
prefetchOrSkip(context.queryClient, snapshotOptions), prefetchOrSkip(context.queryClient, snapshotOptions),
prefetchOrSkip(context.queryClient, schedulesOptions), prefetchOrSkip(context.queryClient, schedulesOptions),
prefetchOrSkip(context.queryClient, statsOptions),
]); ]);
return { return {
...res, ...res,
snapshots: context.queryClient.getQueryData(snapshotOptions.queryKey), snapshots: context.queryClient.getQueryData(snapshotOptions.queryKey),
backupSchedules: context.queryClient.getQueryData(schedulesOptions.queryKey), backupSchedules: context.queryClient.getQueryData(schedulesOptions.queryKey),
stats: context.queryClient.getQueryData(statsOptions.queryKey),
}; };
}, },
validateSearch: type({ tab: "string?" }), validateSearch: type({ tab: "string?" }),
@ -47,13 +51,14 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/")
function RouteComponent() { function RouteComponent() {
const { repositoryId } = Route.useParams(); const { repositoryId } = Route.useParams();
const { snapshots, backupSchedules } = Route.useLoaderData(); const { snapshots, backupSchedules, stats } = Route.useLoaderData();
return ( return (
<RepositoryDetailsPage <RepositoryDetailsPage
repositoryId={repositoryId} repositoryId={repositoryId}
initialSnapshots={snapshots} initialSnapshots={snapshots}
initialBackupSchedules={backupSchedules} initialBackupSchedules={backupSchedules}
initialStats={stats}
/> />
); );
} }