diff --git a/src/pages/courses.tsx b/src/pages/courses.tsx
index aa83c34142ab16507f264b516c19a940a5b8cbb8..ffa6b8d1ff71f89b62b57dd9eb65f13c7a68a9da 100644
--- a/src/pages/courses.tsx
+++ b/src/pages/courses.tsx
@@ -1,4 +1,4 @@
-import { Suspense, useEffect, useRef, useState } from "react";
+import { useEffect, useRef, useState, useTransition } from "react";
 import Link from "next/link";
 import DropdownButton from "react-bootstrap/DropdownButton";
 import DropdownItem from "react-bootstrap/DropdownItem";
@@ -12,7 +12,6 @@ import { useLanguage } from "@/videoag/localization/LanguageProvider";
 import { ReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary";
 import { semesterToHuman } from "@/videoag/miscellaneous/Formatting";
 import { UpdateOverlay } from "@/videoag/miscellaneous/UpdateOverlay";
-import { ResourceType, fetchDataWrapper } from "@/videoag/miscellaneous/PromiseHelpers";
 import { useEditMode } from "@/videoag/object_management/EditModeProvider";
 import {
     OMCreate,
@@ -135,11 +134,11 @@ function CourseList({
     groupBy,
     onlyShowPublicCourses,
 }: {
-    courseData: ResourceType<GetCoursesResponse>;
+    courseData: GetCoursesResponse;
     groupBy: GroupByTypes;
     onlyShowPublicCourses: boolean;
 }) {
-    const data = courseData.read()!;
+    const data = courseData;
 
     const groups = Object.values(data.courses).reduce(
         (acc, course) => {
@@ -149,17 +148,12 @@ function CourseList({
                 if (groupBy === "semester") {
                     groupTitle = semesterToHuman(course.semester, true);
                 }
-                acc[groupKey] = {
-                    groupTitle,
-                    list: [],
-                };
+                acc[groupKey] = { groupTitle, list: [] };
             }
             acc[groupKey].list.push(course);
             return acc;
         },
-        {} as {
-            [key: string]: { groupTitle: string; list: Array<course> };
-        },
+        {} as { [key: string]: { groupTitle: string; list: Array<course> } },
     );
 
     return Object.entries(groups)
@@ -185,7 +179,8 @@ export default function Courses() {
     const authStatus = useAuthStatus();
     const hasUserInfo = authStatus.hasUserInfo();
     const { editMode } = useEditMode();
-    const [courseData, setCourseData] = useState<ResourceType<GetCoursesResponse>>();
+    const [courseData, setCourseData] = useState<GetCoursesResponse>();
+    const [isPending, startTransition] = useTransition();
     const nextCoursesDataPromise = useRef<Promise<GetCoursesResponse> | null>(null);
     const [isReloading, setIsReloading] = useState(false);
     const { language } = useLanguage();
@@ -198,16 +193,24 @@ export default function Courses() {
         const prom = api.getCourses();
         nextCoursesDataPromise.current = prom;
         if (!courseData) {
-            setCourseData(fetchDataWrapper(prom));
+            startTransition(async () => {
+                const data = await prom;
+                startTransition(() => {
+                    setCourseData(data);
+                });
+            });
         } else {
             setIsReloading(true);
-            prom.finally(() => {
+            startTransition(async () => {
+                const data = await prom;
                 if (prom !== nextCoursesDataPromise.current) {
                     console.log("Ignoring stale promise");
                     return;
                 }
-                setCourseData(fetchDataWrapper(prom));
-                setIsReloading(false);
+                startTransition(() => {
+                    setCourseData(data);
+                    setIsReloading(false);
+                });
             });
         }
     };
@@ -217,8 +220,6 @@ export default function Courses() {
 
     const [groupedBy, setGroupedBy] = useState<GroupByTypes>("semester");
 
-    if (courseData === undefined) return <></>;
-
     const groupNames: { name: string; gtype: GroupByTypes }[] = [
         { name: language.get("object.course.semester"), gtype: "semester" },
         { name: language.get("object.course.full_name"), gtype: "full_name" },
@@ -285,14 +286,15 @@ export default function Courses() {
                     </ul>
                 </div>
 
-                <Suspense fallback={<div className="spinner-border" />}>
+                {isPending && !isReloading && <div className="spinner-border" />}
+                {courseData && (
                     <CourseList
                         courseData={courseData}
                         groupBy={groupedBy}
                         onlyShowPublicCourses={onlyShowPublic}
                     />
-                    <UpdateOverlay show={isReloading} />
-                </Suspense>
+                )}
+                <UpdateOverlay show={isReloading} />
             </FallbackErrorBoundary>
         </ReloadBoundary>
     );
diff --git a/src/pages/dynamic_course_listing.tsx b/src/pages/dynamic_course_listing.tsx
index e1955070fc84fbd0d894b742c6aba127cfe33ded..d95a3ed47082714a9a5f82d8b08820524c7d9dda 100644
--- a/src/pages/dynamic_course_listing.tsx
+++ b/src/pages/dynamic_course_listing.tsx
@@ -1,4 +1,4 @@
-import { Suspense, useEffect, useRef, useState } from "react";
+import { useEffect, useRef, useState, useTransition } from "react";
 import { usePathname } from "next/navigation";
 
 import { GetCourseResponse } from "@/videoag/api/types";
@@ -8,7 +8,6 @@ import CourseListing from "@/videoag/course/CourseListing";
 import { Four04, ErrorComponent } from "@/videoag/error/ErrorDisplay";
 import { FallbackErrorBoundary } from "@/videoag/error/FallbackErrorBoundary";
 import { ReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary";
-import { ResourceType, fetchDataWrapper } from "@/videoag/miscellaneous/PromiseHelpers";
 
 function ActualRedirector() {
     const api = useApi();
@@ -16,7 +15,8 @@ function ActualRedirector() {
     const hasUserInfo = authStatus.hasUserInfo();
     const pathname = usePathname();
     const matchedParams = pathname.match(/^\/([a-z0-9_-]+)\/?$/);
-    const [courseData, setCourseData] = useState<ResourceType<GetCourseResponse>>();
+    const [courseData, setCourseData] = useState<GetCourseResponse>();
+    const [isPending, startTransition] = useTransition();
     const nextCourseDataPromise = useRef<Promise<GetCourseResponse> | null>(null);
     const [isReloading, setIsReloading] = useState(false);
 
@@ -29,17 +29,25 @@ function ActualRedirector() {
         const prom = api.getCourse(matchedParams[1], true);
         nextCourseDataPromise.current = prom;
         if (!courseData) {
-            setCourseData(fetchDataWrapper(prom));
+            startTransition(async () => {
+                const data = await prom;
+                startTransition(() => {
+                    setCourseData(data);
+                });
+            });
         } else {
             setIsReloading(true);
-            // already showing course data, disable old one and load new one in the background
-            prom.finally(() => {
+
+            startTransition(async () => {
+                const data = await prom;
                 if (prom !== nextCourseDataPromise.current) {
                     console.log("Ignoring stale promise");
                     return;
                 }
-                setCourseData(fetchDataWrapper(prom));
-                setIsReloading(false);
+                startTransition(() => {
+                    setCourseData(data);
+                    setIsReloading(false);
+                });
             });
         }
     };
@@ -66,9 +74,8 @@ function ActualRedirector() {
                     />
                 )}
             >
-                <Suspense fallback={<div className="spinner-border" />}>
-                    <CourseListing courseData={courseData} disabled={isReloading} />
-                </Suspense>
+                {isPending && <div className="spinner-border" />}
+                {courseData && <CourseListing courseData={courseData} disabled={isReloading} />}
             </FallbackErrorBoundary>
         </ReloadBoundary>
     );
diff --git a/src/pages/dynamic_player.tsx b/src/pages/dynamic_player.tsx
index 9d8a347abd862509ec5c67d7d8da85079f5ce886..89946e4bba36022d33e320228b76ca46477759f1 100644
--- a/src/pages/dynamic_player.tsx
+++ b/src/pages/dynamic_player.tsx
@@ -1,4 +1,4 @@
-import { Suspense, useEffect, useRef, useState } from "react";
+import { useEffect, useRef, useState, useTransition } from "react";
 import { usePathname } from "next/navigation";
 
 import { course, lecture } from "@/videoag/api/types";
@@ -9,7 +9,6 @@ import Player, { EmbeddedPlayer } from "@/videoag/course/Player";
 import { Four04, ErrorComponent } from "@/videoag/error/ErrorDisplay";
 import { FallbackErrorBoundary } from "@/videoag/error/FallbackErrorBoundary";
 import { ReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary";
-import { ResourceType, fetchDataWrapper } from "@/videoag/miscellaneous/PromiseHelpers";
 
 export interface PlayerData {
     course: course;
@@ -37,7 +36,8 @@ export function ActualRedirector() {
     const hasUserInfo = authStatus.hasUserInfo();
     const currentlyAuthenticatedLectureId = authStatus.getCurrentlyAuthenticatedLectureId();
     const pathname = usePathname();
-    const [playerData, setPlayerData] = useState<ResourceType<PlayerData>>();
+    const [playerData, setPlayerData] = useState<PlayerData>();
+    const [isPending, startTransition] = useTransition();
     const nextPlayerDataPromise = useRef<Promise<PlayerData> | null>(null);
     const [isReloading, setIsReloading] = useState(false);
 
@@ -55,16 +55,24 @@ export function ActualRedirector() {
         const prom = dataLoader(api, courseId, lectureId);
         nextPlayerDataPromise.current = prom;
         if (!playerData) {
-            setPlayerData(fetchDataWrapper(prom));
+            startTransition(async () => {
+                const data = await prom;
+                startTransition(() => {
+                    setPlayerData(data);
+                });
+            });
         } else {
             setIsReloading(true);
-            prom.finally(() => {
+            startTransition(async () => {
+                const data = await prom;
                 if (prom !== nextPlayerDataPromise.current) {
                     console.log("Ignoring stale promise");
                     return;
                 }
-                setPlayerData(fetchDataWrapper(prom));
-                setIsReloading(false);
+                startTransition(() => {
+                    setPlayerData(data);
+                    setIsReloading(false);
+                });
             });
         }
     };
@@ -94,14 +102,13 @@ export function ActualRedirector() {
                     />
                 )}
             >
-                <Suspense fallback={<div className="spinner-border" />}>
-                    {matchedParamsEmbed && (
-                        <EmbeddedPlayer playerData={playerData} disabled={isReloading} />
-                    )}
-                    {matchedParamsPlayer && (
-                        <Player playerData={playerData} disabled={isReloading} />
-                    )}
-                </Suspense>
+                {isPending && <div className="spinner-border" />}
+                {matchedParamsEmbed && playerData && (
+                    <EmbeddedPlayer playerData={playerData} disabled={isReloading} />
+                )}
+                {matchedParamsPlayer && playerData && (
+                    <Player playerData={playerData} disabled={isReloading} />
+                )}
             </FallbackErrorBoundary>
         </ReloadBoundary>
     );
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index f7599cfc089a16ff8055e8a82a349a804e832bcc..f1451cdf132c74921ee2ca2e763ef7d22044caa2 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,5 +1,5 @@
-import React from "react";
-import { Suspense, useEffect, useState } from "react";
+import React, { useTransition } from "react";
+import { useEffect, useState } from "react";
 import { useSearchParams } from "next/navigation";
 import { useRouter } from "next/router";
 
@@ -16,11 +16,7 @@ import {
     datetimeToStringOnlyTime,
 } from "@/videoag/miscellaneous/Formatting";
 import { ReloadBoundary, useReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary";
-import {
-    ResourceType,
-    fetchDataWrapper,
-    useDebounceWithArgument,
-} from "@/videoag/miscellaneous/PromiseHelpers";
+import { useDebounceWithArgument } from "@/videoag/miscellaneous/PromiseHelpers";
 import { useEditMode } from "@/videoag/object_management/EditModeProvider";
 import {
     OMCreate,
@@ -46,12 +42,8 @@ function FeatureCard({ data, all_featured }: { data: featured; all_featured: fea
     const movePanel = (dir: int) => {
         const myIndex = all_featured.findIndex((f) => f.id === data.id);
         api.updateOMObject("featured", data.id!, {
-            updates: {
-                display_priority: dir == -1 ? myIndex - 2 : myIndex + 1,
-            },
-            expected_current_values: {
-                display_priority: myIndex,
-            },
+            updates: { display_priority: dir == -1 ? myIndex - 2 : myIndex + 1 },
+            expected_current_values: { display_priority: myIndex },
         })
             .then(reloadFunc)
             .catch((error) => {
@@ -152,19 +144,16 @@ function FeatureCard({ data, all_featured }: { data: featured; all_featured: fea
     );
 }
 
-function FeaturedContent({ homepageData }: { homepageData: ResourceType<GetHomepageResponse> }) {
-    const data = homepageData.read()!;
-
-    return data.featured.map((f) => (
-        <FeatureCard key={f.title} data={f} all_featured={data.featured} />
+function FeaturedContent({ homepageData }: { homepageData: GetHomepageResponse }) {
+    return homepageData.featured.map((f) => (
+        <FeatureCard key={f.title} data={f} all_featured={homepageData.featured} />
     ));
 }
 
-function UpcomingUploads({ homepageData }: { homepageData: ResourceType<GetHomepageResponse> }) {
-    const data = homepageData.read()!;
+function UpcomingUploads({ homepageData }: { homepageData: GetHomepageResponse }) {
     const { language } = useLanguage();
 
-    if (data.upcoming_lectures.length === 0) {
+    if (homepageData.upcoming_lectures.length === 0) {
         return (
             <span className="disable-last-paragraph-spacing">
                 {language.getStyled("ui.index.no_scheduled_recordings", true)}
@@ -173,7 +162,7 @@ function UpcomingUploads({ homepageData }: { homepageData: ResourceType<GetHomep
     }
 
     let dates = []; // group by day/date
-    for (let lecture of data.upcoming_lectures) {
+    for (let lecture of homepageData.upcoming_lectures) {
         let date_string = datetimeToStringOnlyDate(lecture.time);
 
         let date_index = dates.findIndex((date) => date.date === date_string);
@@ -191,7 +180,7 @@ function UpcomingUploads({ homepageData }: { homepageData: ResourceType<GetHomep
                     <strong>{date.date}</strong>
                     <ul className="list-group">
                         {date.lectures.map((lecture, index) => {
-                            let course_data = data.course_context[lecture.course_id];
+                            let course_data = homepageData.course_context[lecture.course_id];
                             return (
                                 <li
                                     className="list-group-item list-group-item-condensed"
@@ -216,13 +205,11 @@ function UpcomingUploads({ homepageData }: { homepageData: ResourceType<GetHomep
     );
 }
 
-function RecentUploads({ homepageData }: { homepageData: ResourceType<GetHomepageResponse> }) {
-    const data = homepageData.read()!;
-
+function RecentUploads({ homepageData }: { homepageData: GetHomepageResponse }) {
     return (
         <ul className="list-group videopreview">
-            {data.latest_lectures.map((lecture, index) => {
-                let course_data = data.course_context[lecture.course_id];
+            {homepageData.latest_lectures.map((lecture, index) => {
+                let course_data = homepageData.course_context[lecture.course_id];
                 return (
                     <li className="list-group-item" key={index}>
                         <LectureCard lecture={lecture} course={course_data} />{" "}
@@ -253,7 +240,8 @@ export default function Home() {
     const api = useApi();
     const authStatus = useAuthStatus();
 
-    const [homepageData, setHomepageData] = useState<ResourceType<GetHomepageResponse>>();
+    const [homepageData, setHomepageData] = useState<GetHomepageResponse>();
+    const [isPending, startTransition] = useTransition();
     const [query, setQuery] = useState("");
     const [updateQueryDeferred, _] = useDebounceWithArgument(setQuery, 500);
 
@@ -284,7 +272,12 @@ export default function Home() {
     useEffect(handleLegacyRedirects, [searchParams, router]);
 
     const reloadData = () => {
-        setHomepageData(fetchDataWrapper(api.getHomepage()));
+        startTransition(async () => {
+            const data = await api.getHomepage();
+            startTransition(() => {
+                setHomepageData(data);
+            });
+        });
     };
 
     useEffect(reloadData, [api, hasUserInfo]);
@@ -316,34 +309,33 @@ export default function Home() {
                         <hr />
                     </>
                 )}
+                {isPending && <div className="spinner-border" />}
                 {homepageData && (
                     <div className="row">
                         {editMode && <ModButtons />}
-                        <Suspense fallback={<div className="spinner-border" />}>
-                            <div className="col-md-6">
-                                <FeaturedContent homepageData={homepageData} />
-                            </div>
-                            <div className="col-md-6">
-                                <div className="card mb-3">
-                                    <div className="card-body">
-                                        <h5 className="card-title">
-                                            {language.get("ui.index.next_recordings")}
-                                        </h5>
-
-                                        <UpcomingUploads homepageData={homepageData} />
-                                    </div>
+                        <div className="col-md-6">
+                            <FeaturedContent homepageData={homepageData} />
+                        </div>
+                        <div className="col-md-6">
+                            <div className="card mb-3">
+                                <div className="card-body">
+                                    <h5 className="card-title">
+                                        {language.get("ui.index.next_recordings")}
+                                    </h5>
+
+                                    <UpcomingUploads homepageData={homepageData} />
                                 </div>
-                                <div className="card mb-3">
-                                    <div className="card-body">
-                                        <h5 className="card-title">
-                                            {language.get("ui.index.recent_videos")}
-                                        </h5>
-
-                                        <RecentUploads homepageData={homepageData} />
-                                    </div>
+                            </div>
+                            <div className="card mb-3">
+                                <div className="card-body">
+                                    <h5 className="card-title">
+                                        {language.get("ui.index.recent_videos")}
+                                    </h5>
+
+                                    <RecentUploads homepageData={homepageData} />
                                 </div>
                             </div>
-                        </Suspense>
+                        </div>
                     </div>
                 )}
             </FallbackErrorBoundary>
diff --git a/src/videoag/course/CourseListing.tsx b/src/videoag/course/CourseListing.tsx
index 79f6be781b40ea739f1154ea4e6c16ab53553fcb..3488d348964e9f68477c35e33e0446c209ca52a3 100644
--- a/src/videoag/course/CourseListing.tsx
+++ b/src/videoag/course/CourseListing.tsx
@@ -4,7 +4,6 @@ import type { GetCourseResponse } from "@/videoag/api/types";
 import { useAuthStatus } from "@/videoag/authentication/AuthStatus";
 import { AuthenticationMethodIcons } from "@/videoag/authentication/ViewPermissions";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
-import { ResourceType } from "@/videoag/miscellaneous/PromiseHelpers";
 import Title from "@/videoag/miscellaneous/TitleComponent";
 import { UpdateOverlay } from "@/videoag/miscellaneous/UpdateOverlay";
 import {
@@ -200,19 +199,15 @@ export default function CourseListing({
     courseData,
     disabled,
 }: {
-    courseData: ResourceType<GetCourseResponse>;
+    courseData: GetCourseResponse;
     disabled: boolean;
 }) {
-    const course = courseData.read()!;
-
-    if (!course) return <>invalid course listing</>;
-
     return (
         <>
-            <Title title={course.full_name} />
+            <Title title={courseData.full_name} />
 
-            <ListingHeader course={course} />
-            <ListingBody course={course} />
+            <ListingHeader course={courseData} />
+            <ListingBody course={courseData} />
             <UpdateOverlay show={disabled} />
         </>
     );
diff --git a/src/videoag/course/Player.tsx b/src/videoag/course/Player.tsx
index 9151c182fdea979ea5ee25751f523292f1123da1..55d52149559eeed5f8f88fa4cdc2bcdf54ed22f5 100644
--- a/src/videoag/course/Player.tsx
+++ b/src/videoag/course/Player.tsx
@@ -15,7 +15,6 @@ import { useApi } from "@/videoag/api/ApiProvider";
 import { ViewPermissionAuthorization } from "@/videoag/authentication/ViewPermissions";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
 import Title from "@/videoag/miscellaneous/TitleComponent";
-import { ResourceType } from "@/videoag/miscellaneous/PromiseHelpers";
 import { UpdateOverlay } from "@/videoag/miscellaneous/UpdateOverlay";
 import { showInfoToast, parseApiDatetime } from "@/videoag/miscellaneous/Util";
 import { useEditMode } from "@/videoag/object_management/EditModeProvider";
@@ -169,9 +168,7 @@ function VideoPlayer({
                         language: lang,
                         userActions: { hotkeys: false },
                         playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 3, 3.5, 4],
-                        controlBar: {
-                            pictureInPictureToggle: false,
-                        },
+                        controlBar: { pictureInPictureToggle: false },
                         plugins: {
                             hotkeys: {
                                 seekStep: 15,
@@ -367,10 +364,10 @@ export function EmbeddedPlayer({
     playerData,
     disabled,
 }: {
-    playerData: ResourceType<PlayerData>;
+    playerData: PlayerData;
     disabled: boolean;
 }) {
-    const { course, lecture } = playerData.read()!;
+    const { course, lecture } = playerData;
 
     useEffect(() => {
         import("video.js");
@@ -414,12 +411,12 @@ export default function Player({
     playerData,
     disabled,
 }: {
-    playerData: ResourceType<PlayerData>;
+    playerData: PlayerData;
     disabled: boolean;
 }) {
     const { language } = useLanguage();
 
-    const { course, lecture } = playerData.read()!;
+    const { course, lecture } = playerData;
 
     useEffect(() => {
         import("video.js");
diff --git a/src/videoag/miscellaneous/PromiseHelpers.tsx b/src/videoag/miscellaneous/PromiseHelpers.tsx
index 8d62cddcf7858febd2ae6e2f50d23174a90cc621..62ef7628189610f2f486dae8cb4426fbd9689344 100644
--- a/src/videoag/miscellaneous/PromiseHelpers.tsx
+++ b/src/videoag/miscellaneous/PromiseHelpers.tsx
@@ -1,64 +1,6 @@
 import { useRef } from "react";
 /* global NodeJS */
 
-export function fetchDataWrapper<T>(prom: Promise<T>) {
-    let status = "pending";
-    let result: T;
-    let suspender = new Promise<void>((resolve, reject) => {
-        prom.then((r) => {
-            status = "success";
-            result = r;
-            resolve();
-        }).catch((e) => {
-            status = "error";
-            result = e;
-            reject();
-        });
-    });
-    return {
-        read() {
-            if (status === "pending") {
-                throw suspender;
-            } else if (status === "error") {
-                throw result;
-            } else if (status === "success") {
-                return result;
-            }
-        },
-    };
-}
-
-export function fetchDataDelayWrapper<T>(prom: Promise<T>, artificialDelay: number) {
-    let status = "pending";
-    let result: T;
-    let suspender = new Promise<void>((resolve, reject) => {
-        setTimeout(() => {
-            prom.then((r) => {
-                status = "success";
-                result = r;
-                resolve();
-            }).catch((e) => {
-                status = "error";
-                result = e;
-                reject();
-            });
-        }, artificialDelay);
-    });
-    return {
-        read() {
-            if (status === "pending") {
-                throw suspender;
-            } else if (status === "error") {
-                throw result;
-            } else if (status === "success") {
-                return result;
-            }
-        },
-    };
-}
-
-export type ResourceType<T> = { read(): T | undefined };
-
 // Returns [deferred, now]
 export function useDebounce<T extends () => any>(func: T, delay: number): [() => void, T] {
     let timeoutId = useRef<NodeJS.Timeout>(undefined);