perf: route- and modal-level code splitting (React.lazy)

The whole app shipped in one bundle, so the logged-out landing and every page
pulled all module code. Split into lazy chunks:
- main.tsx: lazy App + the public leaves (WatchPage, Privacy, Terms), so a
  public /watch share link never downloads the authenticated app.
- App.tsx: each module page (Feed, Channels, Playlists, Stats, Scheduler,
  Config, Users, Settings, Notifications, Messages, Downloads, ChannelPage) and
  the About/ReleaseNotes/Onboarding modals load on demand behind <Suspense>.
- Heavy modals lazy in their parents: PlayerModal (Feed, Playlists),
  DownloadDialog (DownloadButton — kept out of the feed chunk), and the
  VideoEditor/ShareDialog/ProfileEditor (DownloadCenter).
- Extracted focusAccessRequestsTab + the tab constants to lib/adminUsersTab so
  callers can pre-select the admin tab without statically importing the now
  lazy-loaded AdminUsers page (which would defeat the split).

Build now emits ~25 chunks. Landing no longer downloads any module code
(~350 KB deferred); dev landing Perf 93->95. Verified in a real browser: every
page + the video editor load with no console errors.
This commit is contained in:
npeter83 2026-07-04 19:43:50 +02:00
parent 18a33b96c8
commit 2344902a7f
10 changed files with 167 additions and 117 deletions

View file

@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { lazy, Suspense, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
@ -39,7 +39,7 @@ import { formatDuration } from "../lib/format";
import { notify } from "../lib/notifications";
import { getAccountRaw, LS, readAccountMerged, setAccountRaw, writeAccount } from "../lib/storage";
import { useUndoable } from "../lib/useUndoable";
import PlayerModal from "./PlayerModal";
const PlayerModal = lazy(() => import("./PlayerModal"));
import UndoToolbar from "./UndoToolbar";
import { useConfirm } from "./ConfirmProvider";
@ -850,14 +850,16 @@ export default function Playlists({ canWrite }: { canWrite: boolean }) {
</main>
{playingIndex != null && items[playingIndex] && (
<PlayerModal
video={items[playingIndex]}
queue={items}
startIndex={playingIndex}
startAt={null}
onClose={() => setPlayingIndex(null)}
onState={playerState}
/>
<Suspense fallback={null}>
<PlayerModal
video={items[playingIndex]}
queue={items}
startIndex={playingIndex}
startAt={null}
onClose={() => setPlayingIndex(null)}
onState={playerState}
/>
</Suspense>
)}
</div>
);