mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 05:05:38 -04:00
Optimize performance: batch DB ops, N+1 fixes, Suspense streaming, and Jotai best practices
- Add composite indexes on userEpisodeWatches and userMovieWatches for hot queries - Batch episode tracking: wrap season/batch watches in single transaction (~8 queries vs 8*N) - Fix N+1 patterns in credits, recommendations, and filmography with batch prefetch+insert - Stream TV season hydration via Suspense instead of blocking page render - Optimize webhook logs (per-connection LIMIT 10) and system health queries - Merge genre filter waterfalls into single Promise.all fetch - Migrate deprecated Jotai loadable() to unwrap() - Replace isolated createStore()+Provider with useHydrateAtoms on root store - Remove unnecessary atomWithStorage SSR guards (handled by Jotai internally) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import { ensureTvHydrated } from "@/lib/services/metadata";
|
||||
import { TitleSeasons } from "./title-seasons";
|
||||
|
||||
export async function AsyncTitleSeasons({
|
||||
titleId,
|
||||
tmdbId,
|
||||
}: {
|
||||
titleId: string;
|
||||
tmdbId: number;
|
||||
}) {
|
||||
const seasons = await ensureTvHydrated(titleId, tmdbId);
|
||||
if (seasons.length === 0) return null;
|
||||
return <TitleSeasons seasons={seasons} />;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { Hotkey } from "@tanstack/react-hotkeys";
|
||||
import { useHotkey } from "@tanstack/react-hotkeys";
|
||||
import { getDefaultStore, useAtomValue } from "jotai";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { commandPaletteOpenAtom } from "@/lib/atoms/command-palette";
|
||||
import { titleTypeAtom, userStatusAtom } from "@/lib/atoms/title";
|
||||
@@ -15,9 +15,7 @@ export function TitleKeyboardShortcuts() {
|
||||
const { handleStatusChange, handleRating, handleWatchMovie } =
|
||||
useTitleActions();
|
||||
|
||||
const commandPaletteOpen = useAtomValue(commandPaletteOpenAtom, {
|
||||
store: getDefaultStore(),
|
||||
});
|
||||
const commandPaletteOpen = useAtomValue(commandPaletteOpenAtom);
|
||||
const enabled = !commandPaletteOpen;
|
||||
|
||||
// W: toggle watchlist (add if not in library, remove if in library)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { createStore, Provider } from "jotai";
|
||||
import { useState } from "react";
|
||||
import { useHydrateAtoms } from "jotai/utils";
|
||||
import {
|
||||
episodeWatchesAtom,
|
||||
seasonsAtom,
|
||||
@@ -32,17 +31,15 @@ export function TitleProvider({
|
||||
seasons: Season[];
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [store] = useState(() => {
|
||||
const s = createStore();
|
||||
s.set(titleIdAtom, titleId);
|
||||
s.set(titleTypeAtom, titleType);
|
||||
s.set(titleNameAtom, titleName);
|
||||
s.set(seasonsAtom, seasons);
|
||||
s.set(userStatusAtom, initialStatus);
|
||||
s.set(userRatingAtom, initialRating);
|
||||
s.set(episodeWatchesAtom, initialEpisodeWatches);
|
||||
return s;
|
||||
});
|
||||
useHydrateAtoms([
|
||||
[titleIdAtom, titleId],
|
||||
[titleTypeAtom, titleType],
|
||||
[titleNameAtom, titleName],
|
||||
[seasonsAtom, seasons],
|
||||
[userStatusAtom, initialStatus],
|
||||
[userRatingAtom, initialRating],
|
||||
[episodeWatchesAtom, initialEpisodeWatches],
|
||||
]);
|
||||
|
||||
return <Provider store={store}>{children}</Provider>;
|
||||
return children;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
IconChevronUp,
|
||||
IconDeviceTvOld,
|
||||
} from "@tabler/icons-react";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import Image from "next/image";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -29,9 +29,23 @@ import {
|
||||
userStatusAtom,
|
||||
watchingEpAtom,
|
||||
} from "@/lib/atoms/title";
|
||||
import type { Season } from "@/lib/types/title";
|
||||
import { useTitleActions } from "./use-title-actions";
|
||||
|
||||
export function TitleSeasons() {
|
||||
export function TitleSeasons({
|
||||
seasons: streamedSeasons,
|
||||
}: {
|
||||
seasons?: Season[];
|
||||
} = {}) {
|
||||
const setSeasons = useSetAtom(seasonsAtom);
|
||||
|
||||
// When seasons are streamed via Suspense, sync them into the Jotai store
|
||||
useEffect(() => {
|
||||
if (streamedSeasons && streamedSeasons.length > 0) {
|
||||
setSeasons(streamedSeasons);
|
||||
}
|
||||
}, [streamedSeasons, setSeasons]);
|
||||
|
||||
const seasons = useAtomValue(seasonsAtom);
|
||||
const episodeWatches = useAtomValue(episodeWatchesAtom);
|
||||
const userStatus = useAtomValue(userStatusAtom);
|
||||
|
||||
@@ -2,7 +2,10 @@ import { eq } from "drizzle-orm";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { Suspense } from "react";
|
||||
import { RecommendationsSkeleton } from "@/components/skeletons";
|
||||
import {
|
||||
RecommendationsSkeleton,
|
||||
SeasonsSkeleton,
|
||||
} from "@/components/skeletons";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { db } from "@/lib/db/client";
|
||||
import { titles } from "@/lib/db/schema";
|
||||
@@ -10,6 +13,7 @@ import { getTitleWithChildren, importTitle } from "@/lib/services/metadata";
|
||||
import { getUserTitleInfo } from "@/lib/services/tracking";
|
||||
import { tmdbImageUrl } from "@/lib/tmdb/image";
|
||||
import { getTitleThemeStyle } from "@/lib/utils/title-theme";
|
||||
import { AsyncTitleSeasons } from "./_components/async-title-seasons";
|
||||
import { TitleActions } from "./_components/title-actions";
|
||||
import { TitleAvailability } from "./_components/title-availability";
|
||||
import { TitleCast } from "./_components/title-cast";
|
||||
@@ -71,7 +75,7 @@ export default async function TitleDetailPage({
|
||||
]);
|
||||
if (!result) notFound();
|
||||
|
||||
const { title, seasons, availability, cast } = result;
|
||||
const { title, seasons, needsHydration, availability, cast } = result;
|
||||
|
||||
const themeStyle = getTitleThemeStyle(title.colorPalette);
|
||||
|
||||
@@ -94,7 +98,14 @@ export default async function TitleDetailPage({
|
||||
<TitleAvailability availability={availability} />
|
||||
</TitleHero>
|
||||
|
||||
{title.type === "tv" && seasons.length > 0 && <TitleSeasons />}
|
||||
{title.type === "tv" && needsHydration && (
|
||||
<Suspense fallback={<SeasonsSkeleton />}>
|
||||
<AsyncTitleSeasons titleId={title.id} tmdbId={title.tmdbId} />
|
||||
</Suspense>
|
||||
)}
|
||||
{title.type === "tv" && !needsHydration && seasons.length > 0 && (
|
||||
<TitleSeasons />
|
||||
)}
|
||||
|
||||
<TitleCast cast={cast} titleType={title.type} />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user