fix: concurrent stale race, Redis expire leak, missing invalidation, UI Suspense

- Fix integration data cache: set pending synchronously before creating
  fetch to prevent concurrent stale-revalidation from spawning duplicate
  upstream calls
- Redis cache handler: skip persisting entries with expire=0 (dynamic
  entries should not be stored in Redis)
- Add missing invalidation: user.changePassword, group.updateGroup,
  group.transferOwnership
- Move SearchEngineOptimization inside Suspense in root layout
- Add Suspense fallback loader to root layout
- Move searchParams resolution inside Suspense in login page
This commit is contained in:
Thomas Camlong committed 2026-08-07 20:53:36 +02:00
1 parent 38ac8f3075
commit fbb2eddd87
6 files changed
+30 -21

No files matched your search

@@ -19,11 +19,12 @@ interface LoginProps {
}>;
}
async function LoginContent({ searchParams }: { searchParams: { callbackUrl?: string } }) {
async function LoginContent({ searchParams }: { searchParams: Promise<{ callbackUrl?: string }> }) {
const resolvedParams = await searchParams;
const session = await auth();
if (session) {
redirect(sanitizeRedirectionUrl(searchParams.callbackUrl));
redirect(sanitizeRedirectionUrl(resolvedParams.callbackUrl));
}
const t = await getScopedI18n("user.page.login");
@@ -52,7 +53,7 @@ async function LoginContent({ searchParams }: { searchParams: { callbackUrl?: st
providers={env.AUTH_PROVIDERS}
oidcClientName={env.AUTH_OIDC_CLIENT_NAME}
isOidcAutoLoginEnabled={env.AUTH_OIDC_AUTO_LOGIN}
callbackUrl={searchParams.callbackUrl ?? "/"}
callbackUrl={resolvedParams.callbackUrl ?? "/"}
/>
</Card>
</Stack>
@@ -60,9 +61,7 @@ async function LoginContent({ searchParams }: { searchParams: { callbackUrl?: st
);
}
export default async function Login(props: LoginProps) {
const searchParams = await props.searchParams;
export default function Login(props: LoginProps) {
return (
<Suspense
fallback={
@@ -71,7 +70,7 @@ export default async function Login(props: LoginProps) {
</Center>
}
>
<LoginContent searchParams={searchParams} />
<LoginContent searchParams={props.searchParams} />
</Suspense>
);
}
+5 -3
View File
@@ -1,7 +1,7 @@
import type { Metadata, Viewport } from "next";
import { Suspense } from "react";
import { Inter } from "next/font/google";
import { ColorSchemeScript } from "@mantine/core";
import { ColorSchemeScript, Loader } from "@mantine/core";
import "@gfazioli/mantine-onboarding-tour/styles.css";
import "@homarr/notifications/styles.css";
@@ -70,11 +70,13 @@ export default async function Layout(props: {
<html lang={locale} dir={direction} suppressHydrationWarning>
<head>
<ColorSchemeScript />
<SearchEngineOptimization />
<Suspense>
<SearchEngineOptimization />
</Suspense>
<CrowdinLiveTranslation locale={locale} />
</head>
<body className={[fontSans.className, fontSans.variable].join(" ")} suppressHydrationWarning>
<Suspense>
<Suspense fallback={<Loader size="xl" className="absolute inset-0 m-auto" />}>
<SessionScopedProviders>{props.children}</SessionScopedProviders>
</Suspense>
</body>