Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Next.js] Personalize Middleware updates to handle Component A/B Testing #1848

Merged
merged 6 commits into from
Jul 23, 2024
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
9 changes: 3 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Our versioning strategy is as follows:
### 🐛 Bug Fixes

* `[templates/nextjs]` `[templates/react]` `[templates/vue]` `[templates/angular]` Changed formatting in temp/config to prevent parse issues in Unix systems ([#1787](https://github.com/Sitecore/jss/pull/1787))([#1791](https://github.com/Sitecore/jss/pull/1791))
* `[sitecore-jss]` `GraphQLRequestClientFactory` type is updated and `config` parameter is now optional. Since it should match `GraphQLRequestClient.createClientFactory` method return type ([#1806](https://github.com/Sitecore/jss/pull/1806))
* `[templates/nextjs-sxa]` The banner variant of image component is fixed with supporting metadata mode. ([#1826](https://github.com/Sitecore/jss/pull/1826))
* `[sitecore-jss]` `[sitecore-jss-react]` DateField empty value is not treated as empty ([#1836](https://github.com/Sitecore/jss/pull/1836))
* `[templates/nextjs-sxa]` Fix styles of title component in metadata mode. ([#1839](https://github.com/Sitecore/jss/pull/1839))
Expand All @@ -26,7 +27,7 @@ Our versioning strategy is as follows:
* `[sitecore-jss]` _GraphQLRequestClient_ now can accept custom 'headers' in the constructor or via _createClientFactory_ ([#1806](https://github.com/Sitecore/jss/pull/1806))
* `[templates/nextjs]` Removed cors header for API endpoints from _lib/next-config/plugins/cors-header_ plugin since cors is handled by API handlers / middlewares ([#1806](https://github.com/Sitecore/jss/pull/1806))
* `[sitecore-jss-nextjs]` Updates to Next.js editing integration to further support secure hosting scenarios (on XM Cloud & Vercel) ([#1832](https://github.com/Sitecore/jss/pull/1832))
* `[templates/nextjs-xmcloud]` `[sitecore-jss]` AB testing and componente level personalization support. ([#1844](https://github.com/Sitecore/jss/pull/1844))([1847](https://github.com/Sitecore/jss/pull/1847))
* `[templates/nextjs-xmcloud]` `[sitecore-jss]` A/B testing and component-level personalization support. ([#1844](https://github.com/Sitecore/jss/pull/1844))([#1847](https://github.com/Sitecore/jss/pull/1847))([#1848](https://github.com/Sitecore/jss/pull/1848))
* `[sitecore-jss]` `[nextjs-xmcloud]` DictionaryService can now use a `site` GraphQL query instead of `search` one to improve performance. This is currently only available for XMCloud deployments and is enabled with `nextjs-xmcloud` add-on by default ([#1804](https://github.com/Sitecore/jss/pull/1804))([#1846](https://github.com/Sitecore/jss/pull/1846))([commit](https://github.com/Sitecore/jss/commit/5813a2df8ad6a9ee63dd74d5f206ed4b4f758753))([commit](https://github.com/Sitecore/jss/commit/d0ea3ac02c78343b5dd60277dbf7403410794a49))([commit](https://github.com/Sitecore/jss/commit/307b905ed60d7fff44b2dc799fd78c0842af6fbd))([commit](https://github.com/Sitecore/jss/commit/66164a42263aac8b55f0c5e47eda4bd4d7a72e87))
* `[templates/nextjs-sxa]` nextjs-sxa components now use the NextImage component instead of the react Image component from JSS lib for image optimization ([#1843](https://github.com/Sitecore/jss/pull/1843))

Expand All @@ -52,11 +53,7 @@ Our versioning strategy is as follows:
* Updated Angular and core dependencies to ~17.3.11
* Updated Typescript to ~5.2.2
* Updated import statements from zone.js/dist/zone-node to zone.js

### 🐛 Bug Fixes

* `[sitecore-jss]` `GraphQLRequestClientFactory` type is updated and `config` parameter is now optional. Since it should match `GraphQLRequestClient.createClientFactory` method return type ([#1806](https://github.com/Sitecore/jss/pull/1806))

* `[sitecore-jss/personalize]` `[sitecore-jss-nextjs]` `CdpHelper.getPersonalizedRewrite` signature changed to accept `variantIds: string[]` as second parameter. `CdpHelper.getContentId` was renamed to `CdpHelper.getPageFriendlyId`. ([#1848](https://github.com/Sitecore/jss/pull/1848))

### 🧹 Chores

Expand Down
13 changes: 13 additions & 0 deletions docs/upgrades/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,17 @@
personalizeData.componentVariantIds
);
```

illiakovalenko marked this conversation as resolved.
Show resolved Hide resolved
* Update _lib/middleware/plugins/personalize.ts_ `PersonalizeMiddleware` constructor signature, moving `scope` from `cdpConfig` to the root. For now this option will continue working but is marked as deprecated. It will be removed in the next major version release.

```ts
this.personalizeMiddleware = new PersonalizeMiddleware({
...
cdpConfig: {
...
scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, // REMOVE
},
scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, // ADD
});
```

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"dependencies": {
"@sitecore/components": "^1.1.10",
"@sitecore-cloudsdk/events": "^0.3.0",
"@sitecore-cloudsdk/events": "^0.3.1",
"@sitecore-feaas/clientside": "^0.5.17"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { siteResolver } from 'lib/site-resolver';

/**
* This is the personalize middleware plugin for Next.js.
* It is used to enable Sitecore personalization of pages in Next.js.
* It is used to enable Sitecore personalization and A/B testing of pages in Next.js.
*
* The `PersonalizeMiddleware` will
* 1. Make a call to the Sitecore Experience Edge to get the personalization information about the page.
* 2. Based on the response, make a call to the Sitecore CDP (with request/user context) to determine the page variant.
* 3. Rewrite the response to the specific page variant.
* 1. Call Sitecore Experience Edge to get the personalization information about the page.
* 2. Based on the response, call Sitecore Personalize (with request/user context) to determine the page / component variant(s).
* 3. Rewrite the response to the specific page / component variant(s).
*/
class PersonalizePlugin implements MiddlewarePlugin {
private personalizeMiddleware: PersonalizeMiddleware;
Expand All @@ -30,7 +30,6 @@ class PersonalizePlugin implements MiddlewarePlugin {
(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT &&
parseInt(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT)) ||
400,
scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE,
},
// Configuration for your Sitecore CDP endpoint
cdpConfig: {
Expand All @@ -41,6 +40,8 @@ class PersonalizePlugin implements MiddlewarePlugin {
parseInt(process.env.PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT)) ||
400,
},
// Optional Sitecore Personalize scope identifier.
scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE,
// This function determines if the middleware should be turned off.
// IMPORTANT: You should implement based on your cookie consent management solution of choice.
// You may wish to keep it disabled while in development mode.
Expand Down
6 changes: 3 additions & 3 deletions packages/sitecore-jss-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"url": "https://github.com/sitecore/jss/issues"
},
"devDependencies": {
"@sitecore-cloudsdk/personalize": "^0.3.0",
"@sitecore-cloudsdk/personalize": "^0.3.1",
"@types/chai": "^4.3.4",
"@types/chai-as-promised": "^7.1.5",
"@types/chai-string": "^1.4.2",
Expand Down Expand Up @@ -65,8 +65,8 @@
"typescript": "~4.9.4"
},
"peerDependencies": {
"@sitecore-cloudsdk/events": "^0.3.0",
"@sitecore-cloudsdk/personalize": "^0.3.0",
"@sitecore-cloudsdk/events": "^0.3.1",
"@sitecore-cloudsdk/personalize": "^0.3.1",
"next": "^14.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
Expand Down
31 changes: 31 additions & 0 deletions packages/sitecore-jss-nextjs/src/middleware/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,37 @@ describe('MiddlewareBase', () => {
});
});

describe('isPrefetch', () => {
it('should return true when purpose header is prefetch', () => {
const middleware = new SampleMiddleware({ siteResolver: new MockSiteResolver([]) });
const req = createReq({
headerValues: {
purpose: 'prefetch',
},
});

expect(middleware['isPrefetch'](req)).to.equal(true);
});

it('should return true when Next-Router-Prefetch header is 1', () => {
const middleware = new SampleMiddleware({ siteResolver: new MockSiteResolver([]) });
const req = createReq({
headerValues: {
'Next-Router-Prefetch': '1',
},
});

expect(middleware['isPrefetch'](req)).to.equal(true);
});

it('should return false when required header is not provided', () => {
const middleware = new SampleMiddleware({ siteResolver: new MockSiteResolver([]) });
const req = createReq();

expect(middleware['isPrefetch'](req)).to.equal(false);
});
});

describe('excludeRoute', () => {
it('default', () => {
const middleware = new SampleMiddleware({ siteResolver: new MockSiteResolver([]) });
Expand Down
13 changes: 13 additions & 0 deletions packages/sitecore-jss-nextjs/src/middleware/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ export abstract class MiddlewareBase {
);
}

/**
* Determines if the request is a Next.js (next/link) prefetch request
* @param {NextRequest} req request
* @returns {boolean} is prefetch
*/
protected isPrefetch(req: NextRequest): boolean {
return (
// eslint-disable-next-line prettier/prettier
req.headers.get('purpose') === 'prefetch' || // Pages Router
req.headers.get('Next-Router-Prefetch') === '1' // App Router
);
}

protected excludeRoute(pathname: string) {
return (
pathname.startsWith('/api/') || // Ignore Next.js API calls
Expand Down
Loading