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 { ByteSize } from "~/client/components/bytes-size";
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
import type { GetRepositoryStatsResponse } from "~/client/api-client/types.gen";
type Props = {
repositoryShortId: string;
initialStats?: GetRepositoryStatsResponse;
};
const toSafeNumber = (value: number | undefined) => {
@ -16,14 +18,15 @@ const toSafeNumber = (value: number | undefined) => {
return Math.max(0, value);
};
export function CompressionStatsChart({ repositoryShortId }: Props) {
export function CompressionStatsChart({ repositoryShortId, initialStats }: Props) {
const {
data: stats,
isLoading,
isPending,
error,
} = useQuery({
...getRepositoryStatsOptions({ path: { shortId: repositoryShortId } }),
retry: false,
initialData: initialStats,
});
const storedSize = toSafeNumber(stats?.total_size);
@ -40,7 +43,7 @@ export function CompressionStatsChart({ repositoryShortId }: Props) {
const hasStats = !!stats && (storedSize > 0 || uncompressedSize > 0 || snapshotsCount > 0);
if (isLoading) {
if (isPending) {
return (
<Card className="p-6">
<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 { useNavigate, useSearch } from "@tanstack/react-router";
import type { BackupSchedule, Snapshot } from "~/client/lib/types";
import type { GetRepositoryStatsResponse } from "~/client/api-client/types.gen";
export default function RepositoryDetailsPage({
repositoryId,
initialSnapshots,
initialBackupSchedules,
initialStats,
}: {
repositoryId: string;
initialSnapshots?: Snapshot[];
initialBackupSchedules?: BackupSchedule[];
initialStats?: GetRepositoryStatsResponse;
}) {
const navigate = useNavigate();
const { tab } = useSearch({ from: "/(dashboard)/repositories/$repositoryId/" });
@ -32,7 +35,7 @@ export default function RepositoryDetailsPage({
<TabsTrigger value="snapshots">Snapshots</TabsTrigger>
</TabsList>
<TabsContent value="info">
<RepositoryInfoTabContent repository={data} />
<RepositoryInfoTabContent repository={data} initialStats={initialStats} />
</TabsContent>
<TabsContent value="snapshots">
<Suspense>

View file

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

View file

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

View file

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