Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const revalidateResourcesUsingFooter: CollectionAfterChangeHook<Footer> =
});

for (const page of pages.docs) {
revalidatePageCache({ doc: page, locale, payload });
revalidatePageCache({ doc: page, locales: [locale], payload });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const revalidatePagesUsingGlobalBlock: CollectionAfterChangeHook<GlobalBl
});

for (const page of publishedPages) {
revalidatePageCache({ doc: page, locale, payload });
revalidatePageCache({ doc: page, locales: [locale], payload });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const revalidateResourcesUsingHeader: CollectionAfterChangeHook<Header> =
});

for (const page of pages.docs) {
revalidatePageCache({ doc: page, locale, payload });
revalidatePageCache({ doc: page, locales: [locale], payload });
}
}

Expand Down
11 changes: 7 additions & 4 deletions apps/cms/src/collections/Page/hooks/revalidatePage.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook } from "payload";

import { ALL_LOCALES } from "@/lib/config/i18n";
import { getLocaleFromRequest } from "@/lib/utils/getLocaleFromRequest";
import { revalidatePageCache } from "@/lib/utils/revalidatePageCache";
import type { Page } from "@/payload-types";

// `_status` is not localized: publishing, unpublishing or deleting changes the page in every locale.
export const revalidatePage: CollectionAfterChangeHook<Page> = async ({
doc,
previousDoc,
Expand All @@ -13,12 +15,14 @@ export const revalidatePage: CollectionAfterChangeHook<Page> = async ({
const locale = getLocaleFromRequest(req);

if (!context.disableRevalidate) {
const statusChanged = previousDoc?._status !== doc._status;

if (doc._status === "published") {
revalidatePageCache({ doc, locale, payload });
revalidatePageCache({ doc, locales: statusChanged ? ALL_LOCALES : [locale], payload });
}

if (previousDoc?._status === "published" && doc._status !== "published") {
revalidatePageCache({ doc: previousDoc, locale, payload });
revalidatePageCache({ doc: previousDoc, locales: ALL_LOCALES, payload });
}
}

Expand All @@ -28,8 +32,7 @@ export const revalidatePage: CollectionAfterChangeHook<Page> = async ({
export const revalidateDelete: CollectionAfterDeleteHook<Page> = async ({ doc, req }) => {
const { payload, context } = req;
if (!context.disableRevalidate) {
const locale = getLocaleFromRequest(req);
revalidatePageCache({ doc, locale, payload });
revalidatePageCache({ doc, locales: ALL_LOCALES, payload });
}
return doc;
};
19 changes: 11 additions & 8 deletions apps/cms/src/collections/Posts/hooks/revalidatePost.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, Payload } from "payload";

import { ALL_LOCALES } from "@/lib/config/i18n";
import { cacheTag } from "@/lib/utils/cacheTags";
import { revalidateScopedTag } from "@/lib/utils/scopedCache";
import { getLocaleFromRequest } from "@/lib/utils/getLocaleFromRequest";
import type { Locale } from "@/lib/types";
import type { Post } from "@/payload-types";

function revalidatePostTags(slug: string, locale: Locale, payload: Payload) {
function revalidatePostTags(slug: string, locales: readonly Locale[], payload: Payload) {
payload.logger?.info?.(`Revalidating post with slug: ${slug}`);

// Revalidates this post AND all other posts (via postsList tag),
// ensuring related articles sections on other posts stay up to date.
revalidateScopedTag(cacheTag({ locale, slug, type: "post" }), "max");
revalidateScopedTag(cacheTag({ locale, type: "postsList" }), "max");
for (const locale of locales) {
revalidateScopedTag(cacheTag({ locale, slug, type: "post" }), "max");
revalidateScopedTag(cacheTag({ locale, type: "postsList" }), "max");
}
}

export const revalidatePost: CollectionAfterChangeHook<Post> = async ({
Expand All @@ -25,13 +28,15 @@ export const revalidatePost: CollectionAfterChangeHook<Post> = async ({
if (!context.disableRevalidate) {
const locale = getLocaleFromRequest(req);

// `_status` is not localized: a status change or a delete affects every locale.
if (doc._status === "published") {
revalidatePostTags(doc?.slug ?? "", locale, payload);
const statusChanged = previousDoc?._status !== doc._status;
revalidatePostTags(doc?.slug ?? "", statusChanged ? ALL_LOCALES : [locale], payload);
revalidateScopedTag(cacheTag({ type: "sitemap" }), "max");
}

if (previousDoc?._status === "published" && doc._status !== "published") {
revalidatePostTags(previousDoc?.slug ?? "", locale, payload);
revalidatePostTags(previousDoc?.slug ?? "", ALL_LOCALES, payload);
revalidateScopedTag(cacheTag({ type: "sitemap" }), "max");
}
}
Expand All @@ -42,9 +47,7 @@ export const revalidateDelete: CollectionAfterDeleteHook<Post> = async ({ doc, r
const { payload, context } = req;

if (!context.disableRevalidate) {
const locale = getLocaleFromRequest(req);

revalidatePostTags(doc?.slug ?? "", locale, payload);
revalidatePostTags(doc?.slug ?? "", ALL_LOCALES, payload);
revalidateScopedTag(cacheTag({ type: "sitemap" }), "max");
}

Expand Down
2 changes: 2 additions & 0 deletions apps/cms/src/lib/config/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ export const I18N_CONFIG: {
es: "es_ES",
},
};

export const ALL_LOCALES: readonly Locale[] = I18N_CONFIG.locales.map(({ code }) => code);
6 changes: 4 additions & 2 deletions apps/cms/src/lib/utils/revalidatePageCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import { revalidateScopedTag } from "./scopedCache";

export function revalidatePageCache(params: {
doc: Pick<Page, "breadcrumbs" | "slug">;
locale: Locale;
locales: readonly Locale[];
payload: Payload;
}): void {
const path = getPathFromBreadcrumbs(params.doc.breadcrumbs) ?? "home";
params.payload.logger?.info?.(`Revalidating page with slug: ${params.doc.slug}`);
revalidateScopedTag(cacheTag({ locale: params.locale, path, type: "page" }), "max");
for (const locale of params.locales) {
revalidateScopedTag(cacheTag({ locale, path, type: "page" }), "max");
}
revalidateScopedTag(cacheTag({ type: "sitemap" }), "max");
}
58 changes: 58 additions & 0 deletions apps/cms/tests/int/revalidatePage.int.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { beforeEach, describe, expect, it, vi } from "vitest";

import { revalidateScopedTag } from "@/lib/utils/scopedCache";
import { revalidateDelete, revalidatePage } from "@/collections/Page/hooks/revalidatePage";

vi.mock("@/lib/utils/scopedCache", () => ({ revalidateScopedTag: vi.fn() }));

const page = { breadcrumbs: [{ url: "/about" }], slug: "about" };

function args(doc: object, previousDoc: object | undefined, locale?: string) {
return {
doc,
previousDoc,
req: {
context: {},
payload: { logger: { info: vi.fn() } },
searchParams: new URLSearchParams(locale ? { locale } : {}),
},
} as never;
}

function revalidatedTags() {
return vi.mocked(revalidateScopedTag).mock.calls.map(([tag]) => tag);
}

describe("page cache revalidation", () => {
beforeEach(() => {
vi.mocked(revalidateScopedTag).mockClear();
});

it("revalidates every locale when a page is deleted from one locale", async () => {
await revalidateDelete(args(page, undefined, "en"));
expect(revalidatedTags()).toEqual(expect.arrayContaining(["page_about_en", "page_about_es"]));
});

it("revalidates every locale when a page is unpublished", async () => {
await revalidatePage(
args({ ...page, _status: "draft" }, { ...page, _status: "published" }, "en")
);
expect(revalidatedTags()).toEqual(expect.arrayContaining(["page_about_en", "page_about_es"]));
});

it("revalidates every locale when a page is published", async () => {
await revalidatePage(
args({ ...page, _status: "published" }, { ...page, _status: "draft" }, "es")
);
expect(revalidatedTags()).toEqual(expect.arrayContaining(["page_about_en", "page_about_es"]));
});

it("revalidates only the edited locale when a published page changes content", async () => {
await revalidatePage(
args({ ...page, _status: "published" }, { ...page, _status: "published" }, "es")
);
const tags = revalidatedTags();
expect(tags).toContain("page_about_es");
expect(tags).not.toContain("page_about_en");
});
});
Loading