Skip to content

Commit

Permalink
Feature/app router redis provider (#880)
Browse files Browse the repository at this point in the history
<!--
Filling out this template is required. Any PR that does not include
enough information to be reviewed may be closed at a maintainers'
discretion. All new code requires documentation and tests to ensure
against regressions.
-->

### Description of the Change
<!--
We must be able to understand the design of your change from this
description. The maintainer reviewing this PR may not have worked with
this code recently, so please provide as much detail as possible.

Where possible, please also include:
- verification steps to ensure your change has the desired effects and
has not introduced any regressions
- any benefits that will be realized
- any alternative implementations or possible drawbacks that you
considered
- screenshots or screencasts
-->

<!-- Enter any applicable Issue number(s) here that will be
closed/resolved by this PR. -->
Closes #

### How to test the Change
<!-- Please provide steps on how to test or validate that the change in
this PR works as described. -->

### Changelog Entry
<!--
Please include a summary for this PR, noting whether this is something
being Added / Changed / Deprecated / Removed / Fixed / or Security
related. You can replace the sample entries after this comment block
with the single changelog entry line for this PR. -->
> Added - New feature
> Changed - Existing functionality
> Deprecated - Soon-to-be removed feature
> Removed - Feature
> Fixed - Bug fix
> Security - Vulnerability
> Developer - Non-functional update

### Credits
<!-- Please list any and all contributors on this PR so that they can be
added to this projects CREDITS.md file. -->
Props @username, @username2, ...

### Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you are unsure about any of these, please ask for
clarification. We are here to help! -->
- [ ] I agree to follow this project's [**Code of
Conduct**](https://github.com/10up/.github/blob/trunk/CODE_OF_CONDUCT.md).
- [ ] I have updated the documentation accordingly.
- [ ] I have added [Critical Flows, Test Cases, and/or End-to-End
Tests](https://10up.github.io/Open-Source-Best-Practices/testing/) to
cover my change.
- [ ] All new and existing tests pass.
  • Loading branch information
nicholasio authored Jan 16, 2025
1 parent e2e5d7d commit 1c0bf8f
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 33 deletions.
6 changes: 6 additions & 0 deletions .changeset/chilled-pants-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@headstartwp/core": patch
"@headstartwp/next": patch
---

Fix fetch settings for next.js
5 changes: 5 additions & 0 deletions .changeset/short-jokes-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@10up/next-redis-cache-provider": major
---

Introducing support for App Router
16 changes: 10 additions & 6 deletions packages/core/src/data/strategies/AbstractFetchStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,9 @@ export interface FetchResponse<T> {
queriedObject: QueriedObject;
}

type NextJSHeaders = {
next?: {
revalidate?: false | 0 | number;
tags?: string[];
};
type NextJSFetchOptions = {
revalidate?: false | 0 | number;
tags?: string[];
};

/**
Expand Down Expand Up @@ -101,7 +99,9 @@ export interface FetchOptions {
/**
* Headers to sent to fetch
*/
headers?: Record<string, unknown> & NextJSHeaders;
headers?: Record<string, unknown>;

next?: NextJSFetchOptions;
}

export interface FilterDataOptions<T> {
Expand Down Expand Up @@ -337,6 +337,10 @@ export abstract class AbstractFetchStrategy<E, Params extends EndpointParams, R
args.headers = headers;
}

if (options.next) {
args.next = options.next;
}

const result = await apiGet(`${this.baseURL}${url}`, args, burstCache);
const { data } = result.json;

Expand Down
79 changes: 55 additions & 24 deletions packages/next-redis-cache-provider/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ export default class RedisCache implements CacheHandler {

private lazyConnect: boolean = false;

private ctx: CacheHandlerContext;

constructor(ctx: CacheHandlerContext) {
this.ctx = ctx;
this.flushToDisk = ctx.flushToDisk;
this.fs = ctx.fs;
this.serverDistDir = ctx.serverDistDir;
Expand Down Expand Up @@ -125,26 +128,38 @@ 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) {
if (typeof this.BUILD_ID === 'undefined') {
return key;
}

return `${this.BUILD_ID}:${key}`;
}

public async get(
...args: Parameters<IncrementalCache['get']>
): Promise<CacheHandlerValue | null> {
const [key, ctx] = args;
if (ctx?.fetchIdx || ctx?.fetchUrl) {
return null;
}
const [key] = args;

// 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();
Expand All @@ -160,26 +175,42 @@ export default class RedisCache implements CacheHandler {
public async set(...args: Parameters<IncrementalCache['set']>): Promise<void> {
const [key, data, ctx] = args;

if (!this.flushToDisk || !data || ctx.fetchCache) return;
if (!this.flushToDisk || !data) 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}`,
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:${tag}`), key);
}

if (this.lazyConnect) {
this.redisClient.disconnect();
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async revalidateTag(_tag: string): Promise<void> {
// do nothing
async revalidateTag(_tag: string | string[]): Promise<void> {
await this.getBuildIdAndConnect();
const tags = [_tag].flat();

for await (const tag of tags) {
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:${tag}`));
}
}
}
2 changes: 1 addition & 1 deletion packages/next/src/rsc/data/queries/prepareQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function prepareQuery<P extends EndpointParams>(

const options = merge<NextQueryProps<P>['options']>([
{
cache: 'no-store',
cache: typeof rest.options?.next?.revalidate === 'undefined' ? 'no-store' : undefined,
},
rest.options ?? {},
]);
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/rsc/data/seo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions projects/wp-nextjs-app/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,13 @@ 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');
nextConfig.cacheMaxMemorySize = 0;
}

module.exports = withHeadstartWPConfig(nextConfig);
3 changes: 2 additions & 1 deletion projects/wp-nextjs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ async function query({ params }: HeadstartWPRoute) {
params: {
postType: ['post', 'page'],
},
options: {
cache: 'force-cache',
},
});
}

Expand Down
6 changes: 6 additions & 0 deletions projects/wp-nextjs-app/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ async function query({ params }: HeadstartWPRoute) {
slug: 'sample-page',
postType: 'page',
},
options: {
next: {
revalidate: 60,
tags: ['home'],
},
},
});
}

Expand Down

0 comments on commit 1c0bf8f

Please sign in to comment.