Skip to content

Commit

Permalink
[Nextjs] CloudSDK Integration (#1652)
Browse files Browse the repository at this point in the history
* Initial commit

* Handle async Events init call

* Update

* Update

* Updated test

* rename nextjs-personalize -> nextjs-xmcloud. move feaas and BYOC here.

* Move Sitecore Edge Platform / conext related items to nextjs-xmcloud

* Updated context initialization

* Updated dependency

* Updated Yarn.lock

* Fix lint errors

* Updated CHANGELOG

* Repurpose nextjs-personalize -> nextjs-xmcloud initializer "system" template (driven by prompt / --xmcloud CLI option)

* Moved skipping of site information fetch on XM Cloud to base package (GraphQLSiteInfoService)

* CHANGELOG update

* Updated Context implementation, added unit tests

* Updated CHANGELOG

* Updated comment

* Updated cloudsdk to use latest production version

* Updated yarn.lock

* Introduced Bootstrap and pulled nextjs-xmcloud

* Update .env

* Updated jsdoc

* Avoid unused vars rule for Bootstrap

* Normalize sitecoreEdgeUrl

* Updated CHANGELOG

* Updated BYOC initialization

* Updated Props doc

* Updates

* Updated unit test

* Updated comment

* Provided sitecoreEdgeUrl to the middleware

* Refactoring

* Passing props to SDK's

* Updated type

* Renamed SDKs -> sdks

* Updated yarn.lock

---------

Co-authored-by: Adam Brauer <[email protected]>
  • Loading branch information
illiakovalenko and ambrauer authored Nov 8, 2023
1 parent 3468861 commit 6325950
Show file tree
Hide file tree
Showing 32 changed files with 645 additions and 361 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ Our versioning strategy is as follows:
### 🛠 Breaking Changes

* `[create-sitecore-jss]` The `nextjs-personalize` initializer add-on template has been removed and is replaced by the `nextjs-xmcloud` initializer template. You can use the interactive prompts or the `--xmcloud` argument to include this template. ([#1653](https://github.com/Sitecore/jss/pull/1653))
* `[templates/nextjs]` `[sitecore-jss-nextjs]` CloudSDK Integration ([#1652](https://github.com/Sitecore/jss/pull/1652)):
* Removed the following properties from _PersonalizeMiddleware_: _getPointOfSale_, _clientKey_, _endpoint_. You now need to provide _sitecoreEdgeContextId_ as a replacement.
* _PersonalizeMiddleware_ has transitioned to utilizing the _CloudSDK_ package, replacing the previous dependency on _Engage_.
* Introduced _Context_ class, that is used to initialize the application Context and shared Software Development Kits (SDKs). Accessible within the _@sitecore-jss/sitecore-jss-nextjs/context_ submodule.
* Point of Sale resolution is fully removed, now it's handled by Sitecore Edge Proxy

## 21.5.0

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import type { AppProps } from 'next/app';
import { I18nProvider } from 'next-localization';
import { SitecorePageProps } from 'lib/page-props';
import Bootstrap from 'src/Bootstrap';

import 'assets/main.scss';

function App({ Component, pageProps }: AppProps<SitecorePageProps>): JSX.Element {
const { dictionary, ...rest } = pageProps;

return (
// Use the next-localization (w/ rosetta) library to provide our translation dictionary to the app.
// Note Next.js does not (currently) provide anything for translation, only i18n routing.
// If your app is not multilingual, next-localization and references to it can be removed.
<I18nProvider lngDict={dictionary} locale={pageProps.locale}>
<Component {...rest} />
</I18nProvider>
<>
<Bootstrap {...pageProps} />
{/*
// Use the next-localization (w/ rosetta) library to provide our translation dictionary to the app.
// Note Next.js does not (currently) provide anything for translation, only i18n routing.
// If your app is not multilingual, next-localization and references to it can be removed.
*/}
<I18nProvider lngDict={dictionary} locale={pageProps.locale}>
<Component {...rest} />
</I18nProvider>
</>
);
}

Expand Down
10 changes: 0 additions & 10 deletions packages/create-sitecore-jss/src/templates/nextjs-xmcloud/.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,11 @@ SITECORE_EDGE_CONTEXT_ID=

# ==============================================

# Your Sitecore CDP API target (specific to your data center region)
NEXT_PUBLIC_CDP_TARGET_URL=

# Your Sitecore CDP client key
NEXT_PUBLIC_CDP_CLIENT_KEY=

# An optional Sitecore Personalize scope identifier.
# This can be used to isolate personalization data when multiple XM Cloud Environments share a Personalize tenant.
# This should match the PAGES_PERSONALIZE_SCOPE environment variable for your connected XM Cloud Environment.
NEXT_PUBLIC_PERSONALIZE_SCOPE=

# Your Sitecore CDP point(s) of sale
# Can be provided as a single value (mypoint.com) or a multi-value JSON with locales ({"en":"en.mypoint.com","fr":"fr.mypoint.com"} etc)
NEXT_PUBLIC_CDP_POINTOFSALE=

# Timeout (ms) for Sitecore CDP requests to respond within. Default is 400.
PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT=

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"dependencies": {
"@sitecore/components": "~1.0.19",
"@sitecore/engage": "^1.4.1",
"@sitecore/components": "^1.1.0",
"@sitecore-cloudsdk/events": "^0.1.1",
"@sitecore-feaas/clientside": "^0.4.12"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ class EdgePlatformPlugin implements ConfigPlugin {
order = 2;

async exec(config: JssConfig) {
const sitecoreEdgeUrl = process.env[`${constantCase('sitecoreEdgeUrl')}`] || 'https://edge-platform.sitecorecloud.io';
const sitecoreEdgeUrl =
process.env[`${constantCase('sitecoreEdgeUrl')}`]?.replace(/\/$/, '') ||
'https://edge-platform.sitecorecloud.io';
const sitecoreEdgeContextId = process.env[`${constantCase('sitecoreEdgeContextId')}`];

if (config.sitecoreApiKey && sitecoreEdgeContextId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { SitecorePageProps } from 'lib/page-props';
import { context } from 'src/lib/context';
import { siteResolver } from 'lib/site-resolver';
import config from 'temp/config';

/**
* The Bootstrap component is the entry point for performing any initialization logic
* that needs to happen early in the application's lifecycle.
*/
const Bootstrap = (props: SitecorePageProps): JSX.Element | null => {
const site = props.layoutData?.sitecore.context.site;
const siteInfo = siteResolver.getByName(site?.name || config.siteName);

/**
* Initializes the application Context and associated Software Development Kits (SDKs).
* This function is the entry point for setting up the application's context and any SDKs that are required for its proper functioning.
* It prepares the resources needed to interact with various services and features within the application.
*/
context.init({ siteName: siteInfo.name });

return null;
};

export default Bootstrap;
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import * as FEAAS from '@sitecore-feaas/clientside/react';
import dynamic from 'next/dynamic';
import config from 'temp/config';
import { context } from 'lib/context';
/**
* This is an out-of-box bundler for External components (BYOC) (see Sitecore documentation for more details)
* It enables registering components in client-only or SSR/hybrid contexts
* It's recommended to not modify this file - please add BYOC imports in corresponding index.*.ts files instead
*/

// Set context properties to be available within BYOC components
FEAAS.setContextProperties({
sitecoreEdgeUrl: config.sitecoreEdgeUrl,
sitecoreEdgeContextId: config.sitecoreEdgeContextId,
});
FEAAS.setContextProperties(context);

// Import your client-only components via client-bundle. Nextjs's dynamic() call will ensure they are only rendered client-side
const ClientBundle = dynamic(() => import('./index.client'), {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,23 @@
import {
CdpHelper,
LayoutServicePageState,
SiteInfo,
useSitecoreContext,
PosResolver,
} from '@sitecore-jss/sitecore-jss-nextjs';
import { useEffect } from 'react';
import config from 'temp/config';
import { init } from '@sitecore/engage';
import { siteResolver } from 'lib/site-resolver';
import { context } from 'lib/context';

/**
* This is the CDP page view component.
* It uses the Sitecore Engage SDK to enable page view events on the client-side.
* It uses the Sitecore Cloud SDK to enable page view events on the client-side.
* See Sitecore Engage SDK documentation for details.
* https://www.npmjs.com/package/@sitecore/engage
* https://www.npmjs.com/package/@sitecore-cloudsdk/events
*/
const CdpPageView = (): JSX.Element => {
const {
sitecoreContext: { pageState, route, variantId, site },
} = useSitecoreContext();

/**
* Creates a page view event using the Sitecore Engage SDK.
*/
const createPageView = async (
page: string,
language: string,
site: SiteInfo,
pageVariantId: string
) => {
const pointOfSale = PosResolver.resolve(site, language);
const engage = await init({
clientKey: process.env.NEXT_PUBLIC_CDP_CLIENT_KEY || '',
targetURL: process.env.NEXT_PUBLIC_CDP_TARGET_URL || '',
// Replace with the top level cookie domain of the website that is being integrated e.g ".example.com" and not "www.example.com"
cookieDomain: window.location.hostname.replace(/^www\./, ''),
// Cookie may be created in personalize middleware (server), but if not we should create it here
forceServerCookieMode: false,
});
engage.pageView({
channel: 'WEB',
currency: 'USD',
pointOfSale,
page,
pageVariantId,
language,
});
};

/**
* Determines if the page view events should be turned off.
* IMPORTANT: You should implement based on your cookie consent management solution of choice.
Expand All @@ -68,7 +37,6 @@ const CdpPageView = (): JSX.Element => {
return;
}

const siteInfo = siteResolver.getByName(site?.name || config.siteName);
const language = route.itemLanguage || config.defaultLanguage;
const scope = process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE;

Expand All @@ -78,7 +46,16 @@ const CdpPageView = (): JSX.Element => {
variantId as string,
scope
);
createPageView(route.name, language, siteInfo, pageVariantId);

context.getSDK('Events')?.then((Events) =>
Events.pageView({
channel: 'WEB',
currency: 'USD',
page: route.name,
pageVariantId,
language,
})
);
}, [pageState, route, variantId, site]);

return <></>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Context } from '@sitecore-jss/sitecore-jss-nextjs/context';
import config from 'temp/config';

import Events from './sdk/events';

/**
* List of SDKs to be initialized.
* Each SDK is defined as a module with the @type {SDK} type.
*/
const sdks = {
Events,
};

/**
* Context instance that is used to initialize the application Context and associated Software Development Kits (SDKs).
*/
export const context = new Context<typeof sdks>({
sitecoreEdgeUrl: config.sitecoreEdgeUrl,
sitecoreEdgeContextId: config.sitecoreEdgeContextId,
siteName: config.siteName,
sdks,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as Events from '@sitecore-cloudsdk/events/browser';
import { SDK } from '@sitecore-jss/sitecore-jss-nextjs/context';

const sdkModule: SDK<typeof Events> = {
sdk: Events,
init: async (props) => {
// Events module can't be initialized on the server side
// We also don't want to initialize it in development mode
if (typeof window === 'undefined' || process.env.NODE_ENV === 'development') return;

await Events.init({
siteName: props.siteName,
sitecoreEdgeContextId: props.sitecoreEdgeContextId,
// Replace with the top level cookie domain of the website that is being integrated e.g ".example.com" and not "www.example.com"
cookieDomain: window.location.hostname.replace(/^www\./, ''),
// Cookie may be created in personalize middleware (server), but if not we should create it here
enableBrowserCookie: true,
});
},
};

export default sdkModule;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
import { PersonalizeMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware';
import { MiddlewarePlugin } from '..';
import clientFactory from 'lib/graphql-client-factory';
import config from 'temp/config';
import { siteResolver } from 'lib/site-resolver';

/**
Expand Down Expand Up @@ -33,8 +34,8 @@ class PersonalizePlugin implements MiddlewarePlugin {
},
// Configuration for your Sitecore CDP endpoint
cdpConfig: {
endpoint: process.env.NEXT_PUBLIC_CDP_TARGET_URL || '',
clientKey: process.env.NEXT_PUBLIC_CDP_CLIENT_KEY || '',
sitecoreEdgeUrl: config.sitecoreEdgeUrl,
sitecoreEdgeContextId: config.sitecoreEdgeContextId,
timeout:
(process.env.PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT &&
parseInt(process.env.PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT)) ||
Expand All @@ -50,9 +51,6 @@ class PersonalizePlugin implements MiddlewarePlugin {
excludeRoute: () => false,
// Site resolver implementation
siteResolver,
// Personalize middleware will use PosResolver.resolve(site, language) (same as CdpPageView) by default to get point of sale.
// You can also pass a custom point of sale resolver into middleware to override it like so:
// getPointOfSale: (site, language) => { ... }
});
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { SitecorePageProps } from 'lib/page-props';

/**
* The Bootstrap component is the entry point for performing any initialization logic
* that needs to happen early in the application's lifecycle.
*/
const Bootstrap = (_props: SitecorePageProps): JSX.Element | null => {
return null;
};

export default Bootstrap;
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import type { AppProps } from 'next/app';
import { I18nProvider } from 'next-localization';
import { SitecorePageProps } from 'lib/page-props';
import Bootstrap from 'src/Bootstrap';

import 'assets/app.css';

function App({ Component, pageProps }: AppProps<SitecorePageProps>): JSX.Element {
const { dictionary, ...rest } = pageProps;

return (
// Use the next-localization (w/ rosetta) library to provide our translation dictionary to the app.
// Note Next.js does not (currently) provide anything for translation, only i18n routing.
// If your app is not multilingual, next-localization and references to it can be removed.
<I18nProvider lngDict={dictionary} locale={pageProps.locale}>
<Component {...rest} />
</I18nProvider>
<>
<Bootstrap {...pageProps} />
{/*
// Use the next-localization (w/ rosetta) library to provide our translation dictionary to the app.
// Note Next.js does not (currently) provide anything for translation, only i18n routing.
// If your app is not multilingual, next-localization and references to it can be removed.
*/}
<I18nProvider lngDict={dictionary} locale={pageProps.locale}>
<Component {...rest} />
</I18nProvider>
</>
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/sitecore-jss-nextjs/context.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './types/context/index';
1 change: 1 addition & 0 deletions packages/sitecore-jss-nextjs/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/cjs/context/index');
7 changes: 4 additions & 3 deletions packages/sitecore-jss-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"test": "mocha --require ./test/setup.js \"./src/**/*.test.ts\" \"./src/**/*.test.tsx\" --exit",
"prepublishOnly": "npm run build",
"coverage": "nyc npm test",
"generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --readme none --out ../../ref-docs/sitecore-jss-nextjs --entryPoints src/index.ts --entryPoints src/monitoring/index.ts --entryPoints src/editing/index.ts --entryPoints src/middleware/index.ts --githubPages false"
"generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --readme none --out ../../ref-docs/sitecore-jss-nextjs --entryPoints src/index.ts --entryPoints src/monitoring/index.ts --entryPoints src/editing/index.ts --entryPoints src/middleware/index.ts --entryPoints src/context/index.ts --entryPoints src/utils/index.ts --entryPoints src/site/index.ts --entryPoints src/graphql/index.ts --githubPages false"
},
"engines": {
"node": ">=12",
Expand All @@ -30,7 +30,7 @@
"url": "https://github.com/sitecore/jss/issues"
},
"devDependencies": {
"@sitecore/engage": "^1.4.1",
"@sitecore-cloudsdk/personalize": "^0.1.1",
"@types/chai": "^4.3.4",
"@types/chai-as-promised": "^7.1.5",
"@types/chai-string": "^1.4.2",
Expand Down Expand Up @@ -66,7 +66,8 @@
"typescript": "~4.9.4"
},
"peerDependencies": {
"@sitecore/engage": "^1.4.1",
"@sitecore-cloudsdk/events": "^0.1.1",
"@sitecore-cloudsdk/personalize": "^0.1.1",
"next": "^13.4.16",
"react": "^18.2.0",
"react-dom": "^18.2.0"
Expand Down
Loading

0 comments on commit 6325950

Please sign in to comment.