From 2b43e47f3778cb5bbb3d0e32d27cdb6982805f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Tue, 14 Jan 2025 17:39:09 -0300 Subject: [PATCH 1/7] feat: adding support for app router to the redis-cache-provider --- .../next-redis-cache-provider/src/index.ts | 55 +++++++++++++------ packages/next/src/rsc/data/seo.ts | 2 +- projects/wp-nextjs-app/next.config.js | 7 +++ projects/wp-nextjs-app/package.json | 3 +- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/packages/next-redis-cache-provider/src/index.ts b/packages/next-redis-cache-provider/src/index.ts index 1a8671fe5..f193d15a4 100644 --- a/packages/next-redis-cache-provider/src/index.ts +++ b/packages/next-redis-cache-provider/src/index.ts @@ -125,12 +125,26 @@ export default class RedisCache implements CacheHandler { const BUILD_ID = await this.fs.readFile( path.join(path.dirname(this.serverDistDir), 'BUILD_ID'), ); - return BUILD_ID; + + this.BUILD_ID = BUILD_ID.toString(); + + return this.BUILD_ID; } catch (e) { return ''; } } + private async getBuildIdAndConnect() { + return Promise.all([ + this.getBuildId(), + this.lazyConnect ? this.redisClient.connect() : Promise.resolve(), + ]); + } + + private buildKey(key: string) { + return `${this.BUILD_ID}:${key}`; + } + public async get( ...args: Parameters ): Promise { @@ -139,12 +153,9 @@ export default class RedisCache implements CacheHandler { return null; } - // get build id and connect to redis - const [BUILD_ID] = await Promise.all([ - this.getBuildId(), - this.lazyConnect ? this.redisClient.connect() : Promise.resolve(), - ]); - const value = await this.redisClient.get(`${BUILD_ID}:${key}`); + await this.getBuildIdAndConnect(); + + const value = await this.redisClient.get(this.buildKey(key)); if (this.lazyConnect) { this.redisClient.disconnect(); @@ -162,24 +173,36 @@ export default class RedisCache implements CacheHandler { if (!this.flushToDisk || !data || ctx.fetchCache) return; - // get build id and connect to redis - const [BUILD_ID] = await Promise.all([ - this.getBuildId(), - this.lazyConnect ? this.redisClient.connect() : Promise.resolve(), - ]); + await this.getBuildIdAndConnect(); await this.redisClient.set( - `${BUILD_ID}:${key}`, + this.buildKey(key), JSON.stringify({ lastModified: Date.now(), value: data }), ); + const tags = ctx.tags || []; + + for await (const tag of tags) { + await this.redisClient.sadd(this.buildKey(tag), key); + } + if (this.lazyConnect) { this.redisClient.disconnect(); } } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async revalidateTag(_tag: string): Promise { - // do nothing + async revalidateTag(_tag: string | string[]): Promise { + await this.getBuildIdAndConnect(); + const tags = [_tag].flat(); + + for await (const tag of tags) { + const keys = await this.redisClient.smembers(this.buildKey(tag)); + + for await (const key of keys) { + await this.redisClient.del(this.buildKey(key)); + } + + await this.redisClient.del(this.buildKey(tag)); + } } } diff --git a/packages/next/src/rsc/data/seo.ts b/packages/next/src/rsc/data/seo.ts index ded59edd5..84489de5f 100644 --- a/packages/next/src/rsc/data/seo.ts +++ b/packages/next/src/rsc/data/seo.ts @@ -52,7 +52,7 @@ export function fromYoastToMetadata(yoast: YoastJSON, config: HeadlessConfig = { }, twitter: { creator: yoast.twitter_creator, - card: yoast.twitter_card, + card: yoast.twitter_card ?? 'summary', title: yoast.twitter_title, description: yoast.twitter_description, images: yoast.twitter_image, diff --git a/projects/wp-nextjs-app/next.config.js b/projects/wp-nextjs-app/next.config.js index 191842431..bbfb590ef 100644 --- a/projects/wp-nextjs-app/next.config.js +++ b/projects/wp-nextjs-app/next.config.js @@ -9,4 +9,11 @@ const nextConfig = { }, }; +if (process.env.NEXT_REDIS_URL || process.env.VIP_REDIS_PRIMARY) { + // eslint-disable-next-line global-require + const { initRedisClient } = require('@10up/next-redis-cache-provider'); + initRedisClient(); + nextConfig.cacheHandler = require.resolve('@10up/next-redis-cache-provider'); +} + module.exports = withHeadstartWPConfig(nextConfig); diff --git a/projects/wp-nextjs-app/package.json b/projects/wp-nextjs-app/package.json index 21eacd2e0..d2ac3dd91 100644 --- a/projects/wp-nextjs-app/package.json +++ b/projects/wp-nextjs-app/package.json @@ -13,7 +13,8 @@ "react-dom": "^18", "next": "^14.2.5", "@headstartwp/core": "^1.5.0-next.7", - "@headstartwp/next": "^1.5.0-next.8" + "@headstartwp/next": "^1.5.0-next.8", + "@10up/next-redis-cache-provider": "^1.0.0" }, "devDependencies": { "@10up/eslint-config": "^4.0.0", From 9f1ddf2c7b2d55a85209ef847a56a89f8baef199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Tue, 14 Jan 2025 17:52:44 -0300 Subject: [PATCH 2/7] fix: wp plugin warnings --- projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx | 3 +++ projects/wp-nextjs-app/src/app/page.tsx | 3 +++ wp/headless-wp/includes/classes/Integrations/Gutenberg.php | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx b/projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx index 7032465df..55fc1849d 100644 --- a/projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx +++ b/projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx @@ -43,6 +43,9 @@ async function query({ params }: HeadstartWPRoute) { params: { postType: ['post', 'page'], }, + options: { + cache: 'force-cache', + }, }); } diff --git a/projects/wp-nextjs-app/src/app/page.tsx b/projects/wp-nextjs-app/src/app/page.tsx index 944904679..c8d82aa81 100644 --- a/projects/wp-nextjs-app/src/app/page.tsx +++ b/projects/wp-nextjs-app/src/app/page.tsx @@ -9,6 +9,9 @@ async function query({ params }: HeadstartWPRoute) { slug: 'sample-page', postType: 'page', }, + options: { + cache: 'force-cache', + }, }); } diff --git a/wp/headless-wp/includes/classes/Integrations/Gutenberg.php b/wp/headless-wp/includes/classes/Integrations/Gutenberg.php index b30c4fe51..22b072da5 100644 --- a/wp/headless-wp/includes/classes/Integrations/Gutenberg.php +++ b/wp/headless-wp/includes/classes/Integrations/Gutenberg.php @@ -189,7 +189,7 @@ public function process_dom_document_bypassed_block( string $html ): string { * @return DOMDocument */ protected function read_converted_dom_document( string $html ) { - $converted_html = htmlspecialchars_decode( htmlentities( mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ) ) ); + $converted_html = htmlspecialchars_decode( htmlspecialchars( $html, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ) ); $document = new DomDocument( '1.0', 'UTF-8' ); libxml_use_internal_errors( true ); From 54cdb8100ff52d7ae9541e01fd4cdfe79e2be833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Wed, 15 Jan 2025 22:55:40 -0300 Subject: [PATCH 3/7] fix: tags and next fetch options --- .../data/strategies/AbstractFetchStrategy.ts | 16 ++++++---- .../next-redis-cache-provider/src/index.ts | 32 ++++++++++++------- projects/wp-nextjs-app/next.config.js | 1 + projects/wp-nextjs-app/src/app/page.tsx | 4 +++ 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/packages/core/src/data/strategies/AbstractFetchStrategy.ts b/packages/core/src/data/strategies/AbstractFetchStrategy.ts index 615b67342..030fecf6b 100644 --- a/packages/core/src/data/strategies/AbstractFetchStrategy.ts +++ b/packages/core/src/data/strategies/AbstractFetchStrategy.ts @@ -53,11 +53,9 @@ export interface FetchResponse { queriedObject: QueriedObject; } -type NextJSHeaders = { - next?: { - revalidate?: false | 0 | number; - tags?: string[]; - }; +type NextJSFetchOptions = { + revalidate?: false | 0 | number; + tags?: string[]; }; /** @@ -101,7 +99,9 @@ export interface FetchOptions { /** * Headers to sent to fetch */ - headers?: Record & NextJSHeaders; + headers?: Record; + + next?: NextJSFetchOptions; } export interface FilterDataOptions { @@ -337,6 +337,10 @@ export abstract class AbstractFetchStrategy ): Promise { - const [key, ctx] = args; - if (ctx?.fetchIdx || ctx?.fetchUrl) { - return null; - } + const [key] = args; await this.getBuildIdAndConnect(); @@ -171,19 +175,23 @@ export default class RedisCache implements CacheHandler { public async set(...args: Parameters): Promise { const [key, data, ctx] = args; - if (!this.flushToDisk || !data || ctx.fetchCache) return; + if (!this.flushToDisk || !data) return; await this.getBuildIdAndConnect(); - await this.redisClient.set( - this.buildKey(key), - JSON.stringify({ lastModified: Date.now(), value: data }), - ); + const value = JSON.stringify({ lastModified: Date.now(), value: data }); + const redisKey = this.buildKey(key); + + if (typeof ctx.revalidate === 'number') { + await this.redisClient.set(redisKey, value, 'EX', ctx.revalidate); + } else { + await this.redisClient.set(redisKey, value); + } const tags = ctx.tags || []; for await (const tag of tags) { - await this.redisClient.sadd(this.buildKey(tag), key); + await this.redisClient.sadd(this.buildKey(`tag:${tag}`), key); } if (this.lazyConnect) { @@ -196,13 +204,13 @@ export default class RedisCache implements CacheHandler { const tags = [_tag].flat(); for await (const tag of tags) { - const keys = await this.redisClient.smembers(this.buildKey(tag)); + const keys = await this.redisClient.smembers(this.buildKey(`tag:${tag}`)); for await (const key of keys) { await this.redisClient.del(this.buildKey(key)); } - await this.redisClient.del(this.buildKey(tag)); + await this.redisClient.del(this.buildKey(`tag:${tag}`)); } } } diff --git a/projects/wp-nextjs-app/next.config.js b/projects/wp-nextjs-app/next.config.js index bbfb590ef..c54437536 100644 --- a/projects/wp-nextjs-app/next.config.js +++ b/projects/wp-nextjs-app/next.config.js @@ -13,6 +13,7 @@ if (process.env.NEXT_REDIS_URL || process.env.VIP_REDIS_PRIMARY) { // eslint-disable-next-line global-require const { initRedisClient } = require('@10up/next-redis-cache-provider'); initRedisClient(); + nextConfig.cacheHandler = require.resolve('@10up/next-redis-cache-provider'); } diff --git a/projects/wp-nextjs-app/src/app/page.tsx b/projects/wp-nextjs-app/src/app/page.tsx index c8d82aa81..a27c058ee 100644 --- a/projects/wp-nextjs-app/src/app/page.tsx +++ b/projects/wp-nextjs-app/src/app/page.tsx @@ -11,6 +11,10 @@ async function query({ params }: HeadstartWPRoute) { }, options: { cache: 'force-cache', + next: { + revalidate: 60, + tags: ['home'], + }, }, }); } From ac296cc440bd8f4de7fea98e4c30dfb2de5adb26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Thu, 16 Jan 2025 16:44:15 -0300 Subject: [PATCH 4/7] fix: fix next params --- packages/next-redis-cache-provider/src/index.ts | 2 +- packages/next/src/rsc/data/queries/prepareQuery.ts | 2 +- projects/wp-nextjs-app/next.config.js | 1 + projects/wp-nextjs-app/src/app/page.tsx | 1 - 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/next-redis-cache-provider/src/index.ts b/packages/next-redis-cache-provider/src/index.ts index e0d7733f5..68fcb6e83 100644 --- a/packages/next-redis-cache-provider/src/index.ts +++ b/packages/next-redis-cache-provider/src/index.ts @@ -145,7 +145,7 @@ export default class RedisCache implements CacheHandler { } private buildKey(key: string) { - if (this.ctx._appDir) { + if (typeof this.BUILD_ID === 'undefined') { return key; } diff --git a/packages/next/src/rsc/data/queries/prepareQuery.ts b/packages/next/src/rsc/data/queries/prepareQuery.ts index 7aaa933e5..528f46ca5 100644 --- a/packages/next/src/rsc/data/queries/prepareQuery.ts +++ b/packages/next/src/rsc/data/queries/prepareQuery.ts @@ -39,7 +39,7 @@ export function prepareQuery

( const options = merge['options']>([ { - cache: 'no-store', + cache: typeof rest.options?.next?.revalidate === 'undefined' ? 'no-store' : undefined, }, rest.options ?? {}, ]); diff --git a/projects/wp-nextjs-app/next.config.js b/projects/wp-nextjs-app/next.config.js index c54437536..f0f9bb11f 100644 --- a/projects/wp-nextjs-app/next.config.js +++ b/projects/wp-nextjs-app/next.config.js @@ -15,6 +15,7 @@ if (process.env.NEXT_REDIS_URL || process.env.VIP_REDIS_PRIMARY) { initRedisClient(); nextConfig.cacheHandler = require.resolve('@10up/next-redis-cache-provider'); + nextConfig.cacheMaxMemorySize = 0; } module.exports = withHeadstartWPConfig(nextConfig); diff --git a/projects/wp-nextjs-app/src/app/page.tsx b/projects/wp-nextjs-app/src/app/page.tsx index a27c058ee..98ec79f83 100644 --- a/projects/wp-nextjs-app/src/app/page.tsx +++ b/projects/wp-nextjs-app/src/app/page.tsx @@ -10,7 +10,6 @@ async function query({ params }: HeadstartWPRoute) { postType: 'page', }, options: { - cache: 'force-cache', next: { revalidate: 60, tags: ['home'], From dbbe65aee5cb05e0bb784ecd4fab9d62e99a3721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Thu, 16 Jan 2025 16:59:14 -0300 Subject: [PATCH 5/7] fix: php tests --- wp/headless-wp/includes/classes/Integrations/Gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp/headless-wp/includes/classes/Integrations/Gutenberg.php b/wp/headless-wp/includes/classes/Integrations/Gutenberg.php index 22b072da5..07820994d 100644 --- a/wp/headless-wp/includes/classes/Integrations/Gutenberg.php +++ b/wp/headless-wp/includes/classes/Integrations/Gutenberg.php @@ -189,7 +189,7 @@ public function process_dom_document_bypassed_block( string $html ): string { * @return DOMDocument */ protected function read_converted_dom_document( string $html ) { - $converted_html = htmlspecialchars_decode( htmlspecialchars( $html, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ) ); + $converted_html = htmlspecialchars_decode( htmlentities( htmlspecialchars( $html, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ) ) ); $document = new DomDocument( '1.0', 'UTF-8' ); libxml_use_internal_errors( true ); From e9bf544d1a5b2c793c555228a55f17c0136dc38c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Thu, 16 Jan 2025 17:10:21 -0300 Subject: [PATCH 6/7] fix: revert plugin changes --- wp/headless-wp/includes/classes/Integrations/Gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp/headless-wp/includes/classes/Integrations/Gutenberg.php b/wp/headless-wp/includes/classes/Integrations/Gutenberg.php index 07820994d..b30c4fe51 100644 --- a/wp/headless-wp/includes/classes/Integrations/Gutenberg.php +++ b/wp/headless-wp/includes/classes/Integrations/Gutenberg.php @@ -189,7 +189,7 @@ public function process_dom_document_bypassed_block( string $html ): string { * @return DOMDocument */ protected function read_converted_dom_document( string $html ) { - $converted_html = htmlspecialchars_decode( htmlentities( htmlspecialchars( $html, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ) ) ); + $converted_html = htmlspecialchars_decode( htmlentities( mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ) ) ); $document = new DomDocument( '1.0', 'UTF-8' ); libxml_use_internal_errors( true ); From 3164fa8a1515d22a894c219be17cc1fafee0984e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Thu, 16 Jan 2025 18:29:47 -0300 Subject: [PATCH 7/7] chore: add changeset --- .changeset/chilled-pants-suffer.md | 6 ++++++ .changeset/short-jokes-repair.md | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/chilled-pants-suffer.md create mode 100644 .changeset/short-jokes-repair.md diff --git a/.changeset/chilled-pants-suffer.md b/.changeset/chilled-pants-suffer.md new file mode 100644 index 000000000..e6cf50bd8 --- /dev/null +++ b/.changeset/chilled-pants-suffer.md @@ -0,0 +1,6 @@ +--- +"@headstartwp/core": patch +"@headstartwp/next": patch +--- + +Fix fetch settings for next.js diff --git a/.changeset/short-jokes-repair.md b/.changeset/short-jokes-repair.md new file mode 100644 index 000000000..a143f8413 --- /dev/null +++ b/.changeset/short-jokes-repair.md @@ -0,0 +1,5 @@ +--- +"@10up/next-redis-cache-provider": major +--- + +Introducing support for App Router