diff --git a/src/components/CourseListing.tsx b/src/components/CourseListing.tsx
index c8655c044ec26ecb6b57915c67ecd932de5ef43f..5729465e7b09d393b40a74783c999b0695be2840 100644
--- a/src/components/CourseListing.tsx
+++ b/src/components/CourseListing.tsx
@@ -15,6 +15,7 @@ import type { GetCourseResponse, lecture } from "@/api/api_v1_types";
 import { ResourceType } from "@/misc/PromiseHelpers";
 import { AuthenticationMethodIcons } from "@/misc/Util";
 import { useLanguage } from "./LanguageProvider";
+import { UpdateOverlay } from "./UpdateOverlay";
 
 function ListingHeader({ course }: { course: GetCourseResponse }) {
     const { editMode } = useEditMode();
@@ -368,11 +369,10 @@ export default function CourseListing({
     return (
         <>
             <Title title={course.full_name} />
-            <div>
-                <ListingHeader course={course} />
-                <ListingBody course={course} />
-                <div className={"edits-disabled-overlay " + (disabled ? "visible" : "")} />
-            </div>
+
+            <ListingHeader course={course} />
+            <ListingBody course={course} />
+            <UpdateOverlay show={disabled} />
         </>
     );
 }
diff --git a/src/components/Player.tsx b/src/components/Player.tsx
index d633aca3dfe08ee3e332f1eaf4fa85eaf08d674d..5a0032106eaf782aff66f3dc516258eab010ac30 100644
--- a/src/components/Player.tsx
+++ b/src/components/Player.tsx
@@ -36,6 +36,7 @@ import Head from "next/head";
 import { VideoCard } from "./VideoCard";
 import { urlForLecture } from "@/misc/Util";
 import VideoJsMarkers from "@/misc/videojs-markers";
+import { UpdateOverlay } from "./UpdateOverlay";
 
 function initPlayer(
     player: VideoJsPlayerType,
@@ -803,7 +804,13 @@ export function DownloadSources({
     );
 }
 
-export function EmbeddedPlayer({ playerData }: { playerData: ResourceType<PlayerData> }) {
+export function EmbeddedPlayer({
+    playerData,
+    disabled,
+}: {
+    playerData: ResourceType<PlayerData>;
+    disabled: boolean;
+}) {
     const { course, perms, lectureId } = playerData.read()!;
 
     const lecture = course.lectures!.find((l) => l.id === lectureId)!;
@@ -841,10 +848,20 @@ export function EmbeddedPlayer({ playerData }: { playerData: ResourceType<Player
         );
     }
 
+    if (disabled) {
+        return <UpdateOverlay show={true} />;
+    }
+
     return <VideoPlayer lecture={lecture} className="h-100" />;
 }
 
-export default function Player({ playerData }: { playerData: ResourceType<PlayerData> }) {
+export default function Player({
+    playerData,
+    disabled,
+}: {
+    playerData: ResourceType<PlayerData>;
+    disabled: boolean;
+}) {
     const { course, lectureId, perms, loaderHadUserInfo } = playerData.read()!;
     const api = useBackendContext();
     const userContext = useUserContext();
@@ -1026,6 +1043,7 @@ export default function Player({ playerData }: { playerData: ResourceType<Player
                 />
             </Head>
             <Title title={`${course.short_name} - ${lecture.title}`} />
+            <UpdateOverlay show={disabled} />
             <div className={`card ${lecture.is_visible === false ? "bg-danger-subtle" : ""}`}>
                 <div className="card-header d-flex">
                     <div className="flex-fill align-self-center">
diff --git a/src/components/UpdateOverlay.tsx b/src/components/UpdateOverlay.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a44e3678d3b6ba3d4b774f1c4be684544771f8d6
--- /dev/null
+++ b/src/components/UpdateOverlay.tsx
@@ -0,0 +1,19 @@
+export function UpdateOverlay({ show }: { show: boolean }) {
+    return (
+        <div
+            className={
+                "edits-disabled-overlay d-flex flex-column justify-content-center align-items-center " +
+                (show ? "visible" : "")
+            }
+        >
+            <div className="d-flex flex-column align-items-center">
+                <div
+                    className="spinner-grow mb-2"
+                    role="status"
+                    style={{ width: "3rem", height: "3rem" }}
+                />
+                Updating...
+            </div>
+        </div>
+    );
+}
diff --git a/src/pages/courses.tsx b/src/pages/courses.tsx
index 685a2e8b7868e35e8e2f2ec75729642d07c501c2..975e8ca2c9bbbcc07741127e0469aba526dceae6 100644
--- a/src/pages/courses.tsx
+++ b/src/pages/courses.tsx
@@ -8,12 +8,13 @@ import { semesterToHuman } from "@/misc/Formatting";
 import { ResourceType, fetchDataWrapper } from "@/misc/PromiseHelpers";
 import { ErrorPage } from "@/misc/ErrorHandlers";
 import Link from "next/link";
-import { Suspense, useEffect, useState } from "react";
+import { Suspense, useEffect, useRef, useState } from "react";
 import DropdownButton from "react-bootstrap/DropdownButton";
 import DropdownItem from "react-bootstrap/DropdownItem";
 
 import type { GetCoursesResponse, course } from "@/api/api_v1_types";
 import { useLanguage } from "@/components/LanguageProvider";
+import { UpdateOverlay } from "@/components/UpdateOverlay";
 
 type GroupByTypes = "semester" | "full_name" | "organizer" | "topic";
 
@@ -181,13 +182,33 @@ export default function Courses() {
     const hasUserInfo = userContext.hasUserInfo();
     const { editMode } = useEditMode();
     const [courseData, setCourseData] = useState<ResourceType<GetCoursesResponse>>();
+    const nextCoursesDataPromise = useRef<Promise<GetCoursesResponse> | null>(null);
+    const [isReloading, setIsReloading] = useState(false);
     const { language } = useLanguage();
     const [onlyShowPublic, setOnlyShowPublic] = useState(false);
 
     const reloadData = () => {
-        setCourseData(fetchDataWrapper(api.getCourses()));
+        if (isReloading) {
+            console.log("Already reloading, overwriting old request...");
+        }
+        const prom = api.getCourses();
+        nextCoursesDataPromise.current = prom;
+        if (!courseData) {
+            setCourseData(fetchDataWrapper(prom));
+        } else {
+            setIsReloading(true);
+            prom.finally(() => {
+                if (prom !== nextCoursesDataPromise.current) {
+                    console.log("Ignoring stale promise");
+                    return;
+                }
+                setCourseData(fetchDataWrapper(prom));
+                setIsReloading(false);
+            });
+        }
     };
 
+    // eslint-disable-next-line react-hooks/exhaustive-deps
     useEffect(reloadData, [api, hasUserInfo]);
 
     const [groupedBy, setGroupedBy] = useState<GroupByTypes>("semester");
@@ -266,6 +287,7 @@ export default function Courses() {
                         groupBy={groupedBy}
                         onlyShowPublicCourses={onlyShowPublic}
                     />
+                    <UpdateOverlay show={isReloading} />
                 </Suspense>
             </FallbackErrorBoundary>
         </ReloadBoundary>
diff --git a/src/pages/dynamic_course_listing.tsx b/src/pages/dynamic_course_listing.tsx
index 185e1ac2c058317d1221a912d1a6f5a614fa872d..90d449ec8757e41522e015d5f799573c4ebb0c7e 100644
--- a/src/pages/dynamic_course_listing.tsx
+++ b/src/pages/dynamic_course_listing.tsx
@@ -1,4 +1,4 @@
-import { Suspense, useEffect, useState } from "react";
+import { Suspense, useEffect, useRef, useState } from "react";
 import Four04 from "./404";
 import CourseListing from "@/components/CourseListing";
 import { useBackendContext } from "@/components/BackendProvider";
@@ -17,22 +17,29 @@ function ActualRedirector() {
     const pathname = usePathname();
     const matchedParams = pathname.match(/^\/([a-z0-9_-]+)\/?$/);
     const [courseData, setCourseData] = useState<ResourceType<GetCourseResponse>>();
+    const nextCourseDataPromise = useRef<Promise<GetCourseResponse> | null>(null);
     const [isReloading, setIsReloading] = useState(false);
 
     const reload = () => {
         if (!matchedParams) return;
-        if (isReloading) return;
+        if (isReloading) {
+            console.log("Already reloading, overwriting old request...");
+        }
 
         const prom = api.getCourse(matchedParams[1], true);
-        if (!courseData) setCourseData(fetchDataWrapper(prom));
-        else {
-            console.log("Reloading course data");
+        nextCourseDataPromise.current = prom;
+        if (!courseData) {
+            setCourseData(fetchDataWrapper(prom));
+        } else {
             setIsReloading(true);
             // already showing course data, disable old one and load new one in the background
             prom.finally(() => {
+                if (prom !== nextCourseDataPromise.current) {
+                    console.log("Ignoring stale promise");
+                    return;
+                }
                 setCourseData(fetchDataWrapper(prom));
                 setIsReloading(false);
-                console.log("reload done");
             });
         }
     };
diff --git a/src/pages/dynamic_player.tsx b/src/pages/dynamic_player.tsx
index 50ebbdabc8627d3c75e4bf6b60f9c9bb7830f2d4..f23ba366819d7757c3334e55daf6ba4dbe647208 100644
--- a/src/pages/dynamic_player.tsx
+++ b/src/pages/dynamic_player.tsx
@@ -1,4 +1,4 @@
-import { Suspense, useEffect, useState } from "react";
+import { Suspense, useEffect, useRef, useState } from "react";
 import Four04 from "./404";
 
 import { useBackendContext } from "@/components/BackendProvider";
@@ -53,15 +53,32 @@ export function ActualRedirector() {
     const matchedParamsPlayer = pathname.match(/^\/([a-z0-9_-]+)\/([a-z0-9_-]+)$/);
     const matchedParamsEmbed = pathname.match(/^\/([a-z0-9_-]+)\/([a-z0-9_-]+)\/embed$/);
     const [playerData, setPlayerData] = useState<ResourceType<PlayerData>>();
+    const nextPlayerDataPromise = useRef<Promise<PlayerData> | null>(null);
+    const [isReloading, setIsReloading] = useState(false);
 
     const reloadData = () => {
         if (!matchedParamsPlayer && !matchedParamsEmbed) return;
+        if (isReloading) {
+            console.log("Already reloading, overwriting old request...");
+        }
         const matched = (matchedParamsPlayer ?? matchedParamsEmbed)!;
         const courseId = matched[1];
         const lectureId = matched[2];
-        setPlayerData(
-            fetchDataWrapper(dataLoader(api, courseId, lectureId, userContext.hasUserInfo())),
-        );
+        const prom = dataLoader(api, courseId, lectureId, userContext.hasUserInfo());
+        nextPlayerDataPromise.current = prom;
+        if (!playerData) {
+            setPlayerData(fetchDataWrapper(prom));
+        } else {
+            setIsReloading(true);
+            prom.finally(() => {
+                if (prom !== nextPlayerDataPromise.current) {
+                    console.log("Ignoring stale promise");
+                    return;
+                }
+                setPlayerData(fetchDataWrapper(prom));
+                setIsReloading(false);
+            });
+        }
     };
 
     // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -90,8 +107,12 @@ export function ActualRedirector() {
                 )}
             >
                 <Suspense fallback={<div className="spinner-border" />}>
-                    {matchedParamsEmbed && <EmbeddedPlayer playerData={playerData} />}
-                    {matchedParamsPlayer && <Player playerData={playerData} />}
+                    {matchedParamsEmbed && (
+                        <EmbeddedPlayer playerData={playerData} disabled={isReloading} />
+                    )}
+                    {matchedParamsPlayer && (
+                        <Player playerData={playerData} disabled={isReloading} />
+                    )}
                 </Suspense>
             </FallbackErrorBoundary>
         </ReloadBoundary>
diff --git a/src/styles/globals.scss b/src/styles/globals.scss
index a19de2585230426db1ebab2f7789f44adf046a69..ac7fc5efa5e3ce75ebb81bd09d60a72289d88a72 100644
--- a/src/styles/globals.scss
+++ b/src/styles/globals.scss
@@ -286,7 +286,7 @@ $link-decoration: none;
 	background-color: rgba(0, 0, 0, 0.5);
 	z-index: 1000;
 	pointer-events: none;
-	animation: fadeOut 0.1s ease-in-out forwards;
+	animation: fadeOut 0.3s ease-in-out forwards;
 }
 
 .edits-disabled-overlay.visible {