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] JSS Edge Proxy and Context Support #1640

Merged
merged 10 commits into from
Oct 27, 2023
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Our versioning strategy is as follows:

### 🎉 New Features & Improvements

* `[templates/nextjs]` `[sitecore-jss-nextjs]` `[sitecore-jss]` ([#1640](https://github.com/Sitecore/jss/pull/1640)) Sitecore Edge Platform and Context support:
* Introducing the _clientFactory_ property. This property can be utilized by GraphQL-based services, the previously used _endpoint_ and _apiKey_ properties are deprecated. The _clientFactory_ serves as the central hub for executing GraphQL requests within the application.
* New SITECORE_EDGE_CONTEXT_ID environment variable has been added.
* The JSS_APP_NAME environment variable has been updated and is now referred to as SITE_NAME.
* `[templates/nextjs]` Enable client-only BYOC component imports. Client-only components can be imported through src/byoc/index.client.ts. Hybrid (server render with client hydration) components can be imported through src/byoc/index.hybrid.ts. BYOC scaffold logic is also moved from nextjs-sxa addon into base template ([#1628](https://github.com/Sitecore/jss/pull/1628)[#1636](https://github.com/Sitecore/jss/pull/1636))
* `[templates/nextjs]` Import SitecoreForm component into sample nextjs app ([#1628](https://github.com/Sitecore/jss/pull/1628))
* `[sitecore-jss-nextjs]` (Vercel/Sitecore) Deployment Protection Bypass support for editing integration. ([#1634](https://github.com/Sitecore/jss/pull/1634))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import chalk from 'chalk';
import { ConfigPlugin, JssConfig } from '..';
import { GraphQLSiteInfoService, SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs';
import { createGraphQLClientFactory } from 'lib/graphql-client-factory/create';
import { JssConfig } from 'lib/config';
import { ConfigPlugin } from '..';

/**
* This plugin will set the "sites" config prop.
Expand All @@ -13,19 +15,16 @@ class MultisitePlugin implements ConfigPlugin {
async exec(config: JssConfig) {
let sites: SiteInfo[] = [];

const endpoint = config.graphQLEndpoint;
const apiKey = config.sitecoreApiKey;
const endpoint = config.sitecoreEdgeContextId ? config.sitecoreEdgeUrl : config.graphQLEndpoint;

if (!endpoint || !apiKey) {
console.warn(
chalk.yellow('Skipping site information fetch (missing GraphQL endpoint or API key).')
);
if (!endpoint) {
console.warn(chalk.yellow('Skipping site information fetch (missing GraphQL endpoint).'));
} else {
console.log(`Fetching site information from ${endpoint}`);

try {
const siteInfoService = new GraphQLSiteInfoService({
endpoint,
apiKey,
clientFactory: createGraphQLClientFactory(config),
illiakovalenko marked this conversation as resolved.
Show resolved Hide resolved
});
sites = await siteInfoService.fetchSiteInfo();
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class SitePlugin implements Plugin {
: context.params.path ?? '/';

// Get site name (from path)
const siteData = getSiteRewriteData(path, config.jssAppName);
const siteData = getSiteRewriteData(path, config.siteName);

// Resolve site by name
props.site = siteResolver.getByName(siteData.siteName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import config from 'temp/config';
import { SitemapFetcherPlugin } from '..';
import { GetStaticPathsContext } from 'next';
import { siteResolver } from 'lib/site-resolver';
import clientFactory from 'lib/graphql-client-factory';

class GraphqlSitemapServicePlugin implements SitemapFetcherPlugin {
_graphqlSitemapService: MultisiteGraphQLSitemapService;

constructor() {
this._graphqlSitemapService = new MultisiteGraphQLSitemapService({
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
clientFactory,
sites: [...new Set(siteResolver.sites.map((site: SiteInfo) => site.name))],
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const CdpPageView = (): JSX.Element => {
return;
}

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

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

/**
Expand All @@ -24,8 +24,7 @@ class PersonalizePlugin implements MiddlewarePlugin {
this.personalizeMiddleware = new PersonalizeMiddleware({
// Configuration for your Sitecore Experience Edge endpoint
edgeConfig: {
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
clientFactory,
timeout:
(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT &&
parseInt(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT)) ||
Expand All @@ -52,7 +51,7 @@ class PersonalizePlugin implements MiddlewarePlugin {
// 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:
// You can also pass a custom point of sale resolver into middleware to override it like so:
// getPointOfSale: (site, language) => { ... }
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class DefaultPlugin implements SiteResolverPlugin {
exec(sites: SiteInfo[]): SiteInfo[] {
// Add default/configured site
sites.unshift({
name: config.jssAppName,
name: config.siteName,
language: config.defaultLanguage,
hostName: '*',
pointOfSale,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import 'dotenv/config';
import chalk from 'chalk';
import { constants } from '@sitecore-jss/sitecore-jss-nextjs';
import { ConfigPlugin, JssConfig } from '..';
import { JssConfig } from 'lib/config';
import { ConfigPlugin } from '..';

/**
* This plugin will override the "sitecoreApiHost" config prop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const touchToReloadFilePath = 'src/temp/config.js';

const serverOptions = {
appRoot: path.join(__dirname, '..'),
appName: config.jssAppName,
appName: config.siteName,
// Prevent require of ./sitecore/definitions/config.js, because ts-node is running
requireArg: null,
watchPaths: ['./data'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ConfigPlugin, JssConfig } from '..';
import { JssConfig } from 'lib/config';
import { ConfigPlugin } from '..';

/**
* This plugin will set configuration specific for SXA.
* This plugin will set configuration specific for SXA.
*/
class SXAPlugin implements ConfigPlugin {
// should come before fallback
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { NextRequest, NextResponse } from 'next/server';
import { RedirectsMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware';
import config from 'temp/config';
import { MiddlewarePlugin } from '..';
import { siteResolver } from 'lib/site-resolver';
import clientFactory from 'lib/graphql-client-factory';

class RedirectsPlugin implements MiddlewarePlugin {
private redirectsMiddleware: RedirectsMiddleware;
order = 0;

constructor() {
this.redirectsMiddleware = new RedirectsMiddleware({
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
// Client factory implementation
clientFactory,
// These are all the locales you support in your application.
// These should match those in your next.config.js (i18n.locales).
locales: ['en'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { componentBuilder } from 'temp/componentBuilder';
import Layout from 'src/Layout';
import { GetStaticProps } from 'next';
import { siteResolver } from 'lib/site-resolver';
import clientFactory from 'lib/graphql-client-factory';

const Custom404 = (props: SitecorePageProps): JSX.Element => {
if (!(props && props.layoutData)) {
Expand All @@ -27,10 +28,9 @@ const Custom404 = (props: SitecorePageProps): JSX.Element => {
};

export const getStaticProps: GetStaticProps = async (context) => {
const site = siteResolver.getByName(config.jssAppName);
const site = siteResolver.getByName(config.siteName);
const errorPagesService = new GraphQLErrorPagesService({
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
clientFactory,
siteName: site.name,
language: context.locale || config.defaultLanguage,
retries:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { componentBuilder } from 'temp/componentBuilder';
import { GetStaticProps } from 'next';
import config from 'temp/config';
import { siteResolver } from 'lib/site-resolver';
import clientFactory from 'lib/graphql-client-factory';

/**
* Rendered in case if we have 500 error
Expand Down Expand Up @@ -43,10 +44,9 @@ const Custom500 = (props: SitecorePageProps): JSX.Element => {
};

export const getStaticProps: GetStaticProps = async (context) => {
const site = siteResolver.getByName(config.jssAppName);
const site = siteResolver.getByName(config.siteName);
const errorPagesService = new GraphQLErrorPagesService({
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
clientFactory,
siteName: site.name,
language: context.locale || context.defaultLocale || config.defaultLanguage,
retries:
Expand Down
18 changes: 16 additions & 2 deletions packages/create-sitecore-jss/src/templates/nextjs/.env
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ PUBLIC_URL=http://localhost:3000
# We recommend an alphanumeric value of at least 16 characters.
JSS_EDITING_SECRET=

# ===== Sitecore Edge Platform ======

# Your unified Sitecore Edge Context Id.
SITECORE_EDGE_CONTEXT_ID=

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

# ====== Sitecore Preview / Delivery Edge ======
# (Sitecore Edge Proxy environment variables should be set empty, otherwise they will be prioritized and applied)

# Your Sitecore API key is needed to build the app. Typically, the API key is
# defined in `scjssconfig.json` (as `sitecore.apiKey`). This file may not exist
# when building locally (if you've never run `jss setup`), or when building in a
Expand All @@ -36,8 +46,12 @@ SITECORE_API_HOST=
# the resolved Sitecore API hostname + the `graphQLEndpointPath` defined in your `package.json`.
GRAPH_QL_ENDPOINT=

# Your JSS app name (also used as the default site name). Overrides 'config.appName' defined in a package.json
JSS_APP_NAME=
# ================================

# Your Sitecore site name.
# Uses your `package.json` config `appName` if empty.
# When using the Next.js Multisite add-on, the value of the variable represents the default/configured site.
SITE_NAME=

# Your default app language.
DEFAULT_LANGUAGE=
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const plugins = require('scripts/temp/config-plugins');

/**
* JSS configuration object
*/
export interface JssConfig extends Record<string, string | undefined> {
sitecoreApiKey?: string;
sitecoreApiHost?: string;
jssAppName?: string;
graphQLEndpointPath?: string;
defaultLanguage?: string;
graphQLEndpoint?: string;
layoutServiceConfigurationName?: string;
}
import { JssConfig } from 'lib/config';

export interface ConfigPlugin {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ConfigPlugin, JssConfig } from '..';
import { JssConfig } from 'lib/config';
import { ConfigPlugin } from '..';

/**
* This plugin will set computed config props.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ConfigPlugin, JssConfig } from '..';
import chalk from 'chalk';
import { JssConfig } from 'lib/config';
import { ConfigPlugin } from '..';

/**
* This config will set fallback values for properties that were left empty
Expand All @@ -9,10 +11,19 @@ class FallbackPlugin implements ConfigPlugin {
order = 100;

async exec(config: JssConfig) {
if (config.sitecoreApiKey && config.sitecoreEdgeContextId) {
console.log(
chalk.yellow(
"You have configured both 'sitecoreApiKey' and 'sitecoreEdgeContextId' values. The 'sitecoreEdgeContextId' is used instead."
)
);
}

return Object.assign({}, config, {
illiakovalenko marked this conversation as resolved.
Show resolved Hide resolved
defaultLanguage: config.defaultLanguage || 'en',
sitecoreApiKey: config.sitecoreApiKey || 'no-api-key-set',
layoutServiceConfigurationName: config.layoutServiceConfigurationName || 'default',
sitecoreEdgeUrl: config.sitecoreEdgeUrl || 'https://edge-platform.sitecorecloud.io',
});
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ConfigPlugin, JssConfig } from '..';
import { JssConfig } from 'lib/config';
import { ConfigPlugin } from '..';
import packageConfig from 'package.json';

/**
Expand All @@ -11,7 +12,7 @@ class PackageJsonPlugin implements ConfigPlugin {
if (!packageConfig.config) return config;

return Object.assign({}, config, {
jssAppName: config.jssAppName || packageConfig.config.appName,
siteName: config.siteName || packageConfig.config.appName,
graphQLEndpointPath: config.graphQLEndpointPath || packageConfig.config.graphQLEndpointPath,
defaultLanguage: config.defaultLanguage || packageConfig.config.language,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ConfigPlugin, JssConfig } from '..';
import { JssConfig } from 'lib/config';
import { ConfigPlugin } from '..';

/**
* This plugin will set config props based on scjssconfig.json.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import 'dotenv/config';
import fs from 'fs';
import path from 'path';
import { constantCase } from 'constant-case';
import { JssConfig, jssConfigFactory } from './config';
import { JssConfig } from 'lib/config';
import { jssConfigFactory } from './config';

/*
CONFIG GENERATION
Expand All @@ -13,7 +14,10 @@ import { JssConfig, jssConfigFactory } from './config';
const defaultConfig: JssConfig = {
sitecoreApiKey: process.env[`${constantCase('sitecoreApiKey')}`],
sitecoreApiHost: process.env[`${constantCase('sitecoreApiHost')}`],
jssAppName: process.env[`${constantCase('jssAppName')}`],
sitecoreEdgeUrl: process.env[`${constantCase('sitecoreEdgeUrl')}`],
sitecoreEdgeContextId: process.env[`${constantCase('sitecoreEdgeContextId')}`],
siteName:
process.env[`${constantCase('siteName')}`] || process.env[`${constantCase('jssAppName')}`],
graphQLEndpointPath: process.env[`${constantCase('graphQLEndpointPath')}`],
defaultLanguage: process.env[`${constantCase('defaultLanguage')}`],
graphQLEndpoint: process.env[`${constantCase('graphQLEndpoint')}`],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@

// Import your client-only components via client-bundle. Nextjs's dynamic() call will ensure they are only rendered client-side
import dynamic from 'next/dynamic';
import * as FEAAS from '@sitecore-feaas/clientside/react';
import config from 'temp/config';

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

const ClientBundle = dynamic(() => import('./index.client'), {
ssr: false,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Represents the type of config object available within the generated temp/config.js
*/
export interface JssConfig extends Record<string, string | undefined> {
sitecoreApiKey?: string;
sitecoreApiHost?: string;
sitecoreEdgeUrl?: string;
sitecoreEdgeContextId?: string;
siteName?: string;
graphQLEndpointPath?: string;
defaultLanguage?: string;
graphQLEndpoint?: string;
layoutServiceConfigurationName?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
constants,
} from '@sitecore-jss/sitecore-jss-nextjs';
import config from 'temp/config';
import clientFactory from 'lib/graphql-client-factory';

/**
* Factory responsible for creating a DictionaryService instance
Expand All @@ -17,9 +18,8 @@ export class DictionaryServiceFactory {
create(siteName: string): DictionaryService {
return process.env.FETCH_WITH === constants.FETCH_WITH.GRAPHQL
? new GraphQLDictionaryService({
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
siteName,
clientFactory,
/*
The Dictionary Service needs a root item ID in order to fetch dictionary phrases for the current app.
When not provided, the service will attempt to figure out the root item for the current JSS App using GraphQL and app name.
Expand Down
Loading