fix: brand logo caching (#2699)

This commit is contained in:
David Nguyen 2026-04-14 21:18:17 +10:00 committed by GitHub
parent bc82b2e70e
commit 5082226e08
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 52 additions and 16 deletions

View file

@ -98,7 +98,7 @@ export function BrandingPreferencesForm({
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/branding/logo/team/${team?.id}`
: `${NEXT_PUBLIC_WEBAPP_URL()}/api/branding/logo/organisation/${organisation?.id}`;
setPreviewUrl(logoUrl);
setPreviewUrl(logoUrl + '?v=' + Date.now());
setHasLoadedPreview(true);
}
}
@ -173,7 +173,7 @@ export function BrandingPreferencesForm({
/>
<div className="relative flex w-full flex-col gap-y-4">
{!isBrandingEnabled && <div className="bg-background/60 absolute inset-0 z-[9998]" />}
{!isBrandingEnabled && <div className="absolute inset-0 z-[9998] bg-background/60" />}
<FormField
control={form.control}
@ -185,7 +185,7 @@ export function BrandingPreferencesForm({
</FormLabel>
<div className="flex flex-col gap-4">
<div className="border-border bg-background relative h-48 w-full overflow-hidden rounded-lg border">
<div className="relative h-48 w-full overflow-hidden rounded-lg border border-border bg-background">
{previewUrl ? (
<img
src={previewUrl}
@ -193,12 +193,12 @@ export function BrandingPreferencesForm({
className="h-full w-full object-contain p-4"
/>
) : (
<div className="bg-muted/20 dark:bg-muted text-muted-foreground relative flex h-full w-full items-center justify-center text-sm">
<div className="relative flex h-full w-full items-center justify-center bg-muted/20 text-sm text-muted-foreground dark:bg-muted">
<Trans>Please upload a logo</Trans>
{!hasLoadedPreview && (
<div className="bg-muted dark:bg-muted absolute inset-0 z-[999] flex items-center justify-center">
<Loader className="text-muted-foreground h-8 w-8 animate-spin" />
<div className="absolute inset-0 z-[999] flex items-center justify-center bg-muted dark:bg-muted">
<Loader className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)}
</div>
@ -243,7 +243,7 @@ export function BrandingPreferencesForm({
type="button"
variant="link"
size="sm"
className="text-destructive text-xs"
className="text-xs text-destructive"
onClick={() => {
setPreviewUrl('');
onChange(null);

View file

@ -48,12 +48,17 @@ export default function OrganisationSettingsBrandingPage() {
try {
const { brandingEnabled, brandingLogo, brandingUrl, brandingCompanyDetails } = data;
let uploadedBrandingLogo: string | undefined = '';
let uploadedBrandingLogo: string | undefined = undefined;
if (brandingLogo) {
uploadedBrandingLogo = JSON.stringify(await putFile(brandingLogo));
}
// Empty the branding logo if the user unsets it.
if (brandingLogo === null) {
uploadedBrandingLogo = '';
}
await updateOrganisationSettings({
organisationId: organisation.id,
data: {

View file

@ -34,12 +34,13 @@ export default function TeamsSettingsPage() {
try {
const { brandingEnabled, brandingLogo, brandingUrl, brandingCompanyDetails } = data;
let uploadedBrandingLogo = teamWithSettings?.teamSettings?.brandingLogo;
let uploadedBrandingLogo: string | undefined = undefined;
if (brandingLogo) {
uploadedBrandingLogo = JSON.stringify(await putFile(brandingLogo));
}
// Empty the branding logo if the user unsets it.
if (brandingLogo === null) {
uploadedBrandingLogo = '';
}
@ -48,7 +49,7 @@ export default function TeamsSettingsPage() {
teamId: team.id,
data: {
brandingEnabled,
brandingLogo: uploadedBrandingLogo || null,
brandingLogo: uploadedBrandingLogo,
brandingUrl: brandingUrl || null,
brandingCompanyDetails: brandingCompanyDetails || null,
},

View file

@ -1,10 +1,13 @@
import { sha256 } from '@documenso/lib/universal/crypto';
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
import { loadLogo } from '@documenso/lib/utils/images/logo';
import { prisma } from '@documenso/prisma';
import type { Route } from './+types/branding.logo.organisation.$orgId';
export async function loader({ params }: Route.LoaderArgs) {
const CACHE_CONTROL = 'public, max-age=0, stale-while-revalidate=86400';
export async function loader({ params, request }: Route.LoaderArgs) {
const organisationId = params.orgId;
if (!organisationId) {
@ -48,6 +51,18 @@ export async function loader({ params }: Route.LoaderArgs) {
);
}
const etag = `"${Buffer.from(sha256(settings.brandingLogo)).toString('hex')}"`;
if (request.headers.get('If-None-Match') === etag) {
return new Response(null, {
status: 304,
headers: {
ETag: etag,
'Cache-Control': CACHE_CONTROL,
},
});
}
const file = await getFileServerSide(JSON.parse(settings.brandingLogo)).catch((e) => {
console.error(e);
});
@ -68,8 +83,8 @@ export async function loader({ params }: Route.LoaderArgs) {
headers: {
'Content-Type': contentType,
'Content-Length': content.length.toString(),
// Stale while revalidate for 1 hours to 24 hours
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
'Cache-Control': CACHE_CONTROL,
ETag: etag,
},
});
}

View file

@ -1,10 +1,13 @@
import { getTeamSettings } from '@documenso/lib/server-only/team/get-team-settings';
import { sha256 } from '@documenso/lib/universal/crypto';
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
import { loadLogo } from '@documenso/lib/utils/images/logo';
import type { Route } from './+types/branding.logo.team.$teamId';
export async function loader({ params }: Route.LoaderArgs) {
const CACHE_CONTROL = 'public, max-age=0, stale-while-revalidate=86400';
export async function loader({ params, request }: Route.LoaderArgs) {
const teamId = Number(params.teamId);
if (teamId === 0 || Number.isNaN(teamId)) {
@ -41,6 +44,18 @@ export async function loader({ params }: Route.LoaderArgs) {
);
}
const etag = `"${Buffer.from(sha256(settings.brandingLogo)).toString('hex')}"`;
if (request.headers.get('If-None-Match') === etag) {
return new Response(null, {
status: 304,
headers: {
ETag: etag,
'Cache-Control': CACHE_CONTROL,
},
});
}
const file = await getFileServerSide(JSON.parse(settings.brandingLogo)).catch((e) => {
console.error(e);
});
@ -61,8 +76,8 @@ export async function loader({ params }: Route.LoaderArgs) {
headers: {
'Content-Type': contentType,
'Content-Length': content.length.toString(),
// Stale while revalidate for 1 hours to 24 hours
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
'Cache-Control': CACHE_CONTROL,
ETag: etag,
},
});
}