Skip to content

Commit

Permalink
[Next.js] Multi-site implementation (#1288)
Browse files Browse the repository at this point in the history
* [Next.js][Multi-site] Create plugin approach for extract-path

* Minor changes to JSDoc

* [Next.js][Multi-site] Dynamic site resolver (#1271)

* [Next.js][Multi-site] Dynamic site resolver

* add changes

* Add unit tests, parse pattern

* etc

* simplify reg exp

* [Next.js][Multi-site] Multi-site path utils (#1275)

* [Next.js][Multi-site] Multi-site path utils

* Add return type to `getSiteRewriteData`

Co-authored-by: Adam Brauer <400763+ambrauer@users.noreply.github.com>

* [Next.js][Multi-site] Fetch site info during build (#1276)

* GraphQL site info service (#1227)

* GraphQL site info service

* some tweaks

* more tweaks: debug, query

* query fix for xm cloud

* move template id to contstants

* [Next.js][Multi-site] Dynamic site resolver (#1224)

* [Next.js][Multi-site] Dynamic site resolver

* Use SiteInfo from graphql-siteinfo-service

Co-authored-by: Adam Brauer <400763+ambrauer@users.noreply.github.com>

* Plugin approach for JSS config generation. Added multisite plugin to set the "sites" prop.

* Standardized comments in config plugins. Misc fixes.

* Fixed tests and relocated headlessSiteGroupingTemplate constant (since this query is temp anyway)

* Moved defaultLanguage of 'en' to defaultConfig

* Simplify config plugin order. Delete multisite-sample.ts

Co-authored-by: Artem Alexeyenko <ala@sitecore.net>
Co-authored-by: Illia Kovalenko <23364749+illiakovalenko@users.noreply.github.com>

* [Next.js][Multi-site] Site resolution in API routes (#1277)

* add site resolution in api routes

* re-exported SiteResolver from nextjs package

* passed siteInfo to siteResolver

* refactored based on changes in resolver function

* [Next.js][Multi-site] Site resolution in page props factory (#1281)

* SiteResolver.resolve updates: removed 'language' from site resolution logic, return SiteInfo instead of site name

* Another refactor of SiteResolver. It now must be instantiated with the array of SiteInfo passed. Also introduced a way to resolve by site name ("getByName"). The old "resolve" became "getByHost".

* Added "site" prop to page props and resolve for both base nextjs initializer (single site) and nextjs-multisite add-on.

* misc lint fixes

* Adjustments to API routes based on latest SiteResolver changes

* Added comments

* Removed generateConfig from fetch-graphql-introspection-data.ts. No longer works after config generation refactor, and not necessary anyway since we provide details on how to generate config in error message anyway

* Update page props factory error-pages plugin to use props.site.name

* [Next.js][Multi-site] Multi-site middleware plugin (#1279)

* jss-cli unit test coverage: first batch

* jss-cli unit test coverage: second batch

* jss-cli unit tests: second batch

* fix expected output for test not to fail across environments

* lint

* dev-tools unit test coverage first batch

* dev-tools unit test coverage: second batch

* resolve-scjssconfig test placeholder (for the future!)

* exclude index file from jss-vue test coverage (#1266)

* version v21.1.0-canary.58 [skip ci]

* fix test for scjssconfig resolving

* Update packages/sitecore-jss-dev-tools/src/manifest/manifest-manager.test.ts

Co-authored-by: Adam Brauer <400763+ambrauer@users.noreply.github.com>

* Update packages/sitecore-jss-dev-tools/src/manifest/manifest-manager.test.ts

Co-authored-by: Adam Brauer <400763+ambrauer@users.noreply.github.com>

* lint

* adding test data for scjssconfig

* some improvements to reject logic in resolve scjssconfig

* lint numero 2

* version v21.1.0-canary.59 [skip ci]

* #556667: fixed urls for sitemap

* version v21.1.0-canary.60 [skip ci]

* final batch

* re-gen yarn.lock

* yarn.lock re-update

* version v21.1.0-canary.61 [skip ci]

* #552985: fixed header styles

* version v21.1.0-canary.62 [skip ci]

* #546298: fixed style for showing hidden components

* version v21.1.0-canary.63 [skip ci]

* SiteResolver.resolve updates: removed 'language' from site resolution logic, return SiteInfo instead of site name

* [Next.js][Multi-site] Multi-site middleware plugin

* #559044: fixed rendering dynamic placeholder (#1278)

* version v21.1.0-canary.64 [skip ci]

* Adjust with latest site resolver changes

* adjust

* Add latest changes

* Extra comment

* extra fix

* Extend unit tests

* Revert cookie set change

* Use response cookies instead of request

* Adjust changes according to review

* Adjust changes according to review

* lint fix

Co-authored-by: Artem Alexeyenko <ala@sitecore.net>
Co-authored-by: Automated Build <builds@sitecore.com>
Co-authored-by: Adam Brauer <400763+ambrauer@users.noreply.github.com>
Co-authored-by: Ruslan Matkovskyi <rusm@sitecore.net>
Co-authored-by: Ruslan Matkovskyi <100142572+matkovskyi@users.noreply.github.com>

* [Next.js][Multi-site] Support for "sc_site" query string parameter (#1283)

* jss-cli unit test coverage: first batch

* jss-cli unit test coverage: second batch

* jss-cli unit tests: second batch

* fix expected output for test not to fail across environments

* lint

* dev-tools unit test coverage first batch

* dev-tools unit test coverage: second batch

* resolve-scjssconfig test placeholder (for the future!)

* exclude index file from jss-vue test coverage (#1266)

* version v21.1.0-canary.58 [skip ci]

* fix test for scjssconfig resolving

* Update packages/sitecore-jss-dev-tools/src/manifest/manifest-manager.test.ts

Co-authored-by: Adam Brauer <400763+ambrauer@users.noreply.github.com>

* Update packages/sitecore-jss-dev-tools/src/manifest/manifest-manager.test.ts

Co-authored-by: Adam Brauer <400763+ambrauer@users.noreply.github.com>

* lint

* adding test data for scjssconfig

* some improvements to reject logic in resolve scjssconfig

* lint numero 2

* version v21.1.0-canary.59 [skip ci]

* #556667: fixed urls for sitemap

* version v21.1.0-canary.60 [skip ci]

* final batch

* re-gen yarn.lock

* yarn.lock re-update

* version v21.1.0-canary.61 [skip ci]

* #552985: fixed header styles

* version v21.1.0-canary.62 [skip ci]

* #546298: fixed style for showing hidden components

* version v21.1.0-canary.63 [skip ci]

* SiteResolver.resolve updates: removed 'language' from site resolution logic, return SiteInfo instead of site name

* [Next.js][Multi-site] Multi-site middleware plugin

* #559044: fixed rendering dynamic placeholder (#1278)

* version v21.1.0-canary.64 [skip ci]

* Adjust with latest site resolver changes

* adjust

* Add latest changes

* Extra comment

* extra fix

* Extend unit tests

* Revert cookie set change

* Use response cookies instead of request

* Add querystring support

* Adjust changes according to review

* Adjust changes

* Adjust changes according to review

* lint fix

* Use `sc_site` request cookie when it's present

Co-authored-by: Artem Alexeyenko <ala@sitecore.net>
Co-authored-by: Automated Build <builds@sitecore.com>
Co-authored-by: Adam Brauer <400763+ambrauer@users.noreply.github.com>
Co-authored-by: Ruslan Matkovskyi <rusm@sitecore.net>
Co-authored-by: Ruslan Matkovskyi <100142572+matkovskyi@users.noreply.github.com>

* Import SiteResolver from `middleware` submodule

* Ensure site and variant prefixes are position agnostic (#1286)

* personalize rewrite unit tests

(cherry picked from commit 3c412ed6855d60452e1e29d21a92d1901312d3d4)

* site path utils unit tests, tweak to personalize path unit test

* [MultiSite] SSG support (#1284)

* added ssg support for multisite

* added changes from review comments

* added sitename for debug log

* refactored sitemap methods

* fixed debug sitename

* add error when no sites

* removed commented code

* refactored transformLanguageSitePaths

* Update packages/sitecore-jss-nextjs/src/services/graphql-sitemap-service.ts

Co-authored-by: Adam Brauer <400763+ambrauer@users.noreply.github.com>

* lint fix

Co-authored-by: illiakovalenko <illia.kovalenko@sitecore.com>
Co-authored-by: Illia Kovalenko <23364749+illiakovalenko@users.noreply.github.com>
Co-authored-by: Artem Alexeyenko <ala@sitecore.net>
Co-authored-by: Addy Pathania <89087450+sc-addypathania@users.noreply.github.com>
Co-authored-by: Automated Build <builds@sitecore.com>
Co-authored-by: Ruslan Matkovskyi <rusm@sitecore.net>
Co-authored-by: Ruslan Matkovskyi <100142572+matkovskyi@users.noreply.github.com>
  • Loading branch information
8 people authored Jan 12, 2023
1 parent b0aa767 commit c4c7cd6
Show file tree
Hide file tree
Showing 64 changed files with 2,700 additions and 368 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'dotenv/config';
import chalk from 'chalk';
import { ConfigPlugin, JssConfig } from '..';
import { GraphQLSiteInfoService, SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs';

/**
* This plugin will set the "sites" config prop.
* By default this will attempt to fetch site information directly from Sitecore (using the GraphQLSiteInfoService).
* You could easily modify this to fetch from another source such as a static JSON file instead.
*/
class MultisitePlugin implements ConfigPlugin {
order = 3;

async exec(config: JssConfig) {
let sites: SiteInfo[] = [];
const endpoint = process.env.GRAPH_QL_ENDPOINT || config.graphQLEndpoint;
const apiKey = process.env.SITECORE_API_KEY || config.sitecoreApiKey;

if (!endpoint || !apiKey) {
console.warn(
chalk.yellow('Skipping site information fetch (missing GraphQL connection details)')
);
} else {
console.log(`Fetching site information from ${endpoint}`);
try {
const siteInfoService = new GraphQLSiteInfoService({
endpoint,
apiKey,
});
sites = await siteInfoService.fetchSiteInfo();
} catch (error) {
console.error(chalk.red('Error fetching site information'));
console.error(error);
}
}

return Object.assign({}, config, {
sites: JSON.stringify(sites),
});
}
}

export const multisitePlugin = new MultisitePlugin();
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { normalizeSiteRewrite } from '@sitecore-jss/sitecore-jss-nextjs';
import { Plugin } from '..';

class MultisitePlugin implements Plugin {
exec(path: string) {
// Remove site rewrite segment from the path
return normalizeSiteRewrite(path);
}
}

export const multisitePlugin = new MultisitePlugin();
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { NextRequest, NextResponse } from 'next/server';
import { MultisiteMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware';
import { siteResolver } from 'lib/site-resolver';
import { MiddlewarePlugin } from '..';

/**
* This is the multisite middleware plugin for Next.js.
* It is used to enable Sitecore multisite in Next.js.
*
* The `MultisiteMiddleware` will
* 1. Based on provided hostname and sites information, resolve site.
* 2. Rewrite the response to the specific site.
* 3. Set `sc_site` cookie with site name and `x-sc-rewrite` header with rewritten path to be reused in following middlewares.
*/
class MultisitePlugin implements MiddlewarePlugin {
private multisiteMiddleware: MultisiteMiddleware;

// Multisite middleware has to be executed first
order = -1;

constructor() {
this.multisiteMiddleware = new MultisiteMiddleware({
// This function determines if a route should be excluded from site resolution.
// Certain paths are ignored by default (e.g. files and Next.js API routes), but you may wish to exclude more.
// This is an important performance consideration since Next.js Edge middleware runs on every request.
excludeRoute: () => false,
// This function resolves site based on hostname
getSite: siteResolver.getByHost,
});
}

async exec(req: NextRequest, res?: NextResponse): Promise<NextResponse> {
return this.multisiteMiddleware.getHandler()(req, res);
}
}

export const multisitePlugin = new MultisitePlugin();
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { SitecorePageProps } from 'lib/page-props';
import { GetServerSidePropsContext, GetStaticPropsContext } from 'next';
import { getSiteRewriteData } from '@sitecore-jss/sitecore-jss-nextjs';
import { Plugin } from '..';
import { siteResolver } from 'lib/site-resolver';
import config from 'temp/config';

class SitePlugin implements Plugin {
order = 0;

async exec(props: SitecorePageProps, context: GetServerSidePropsContext | GetStaticPropsContext) {
const path =
context.params === undefined
? '/'
: Array.isArray(context.params.path)
? context.params.path.join('/')
: context.params.path ?? '/';

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

// Resolve site by name
props.site = siteResolver.getByName(siteData.siteName);

return props;
}
}

export const sitePlugin = new SitePlugin();
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SiteResolver, SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs/middleware';
import config from 'temp/config';

/*
The site resolver stores site information and is used in the app
whenever site lookup is required (e.g. by name in page props factory
or by host in Next.js middleware).
*/

// Grab our configured sites
const sites = JSON.parse(config.sites) as SiteInfo[];

// Then add fallback site with default values
sites.push({
name: config.jssAppName,
language: config.defaultLanguage,
hostName: '*',
});

/** SiteResolver singleton */
export const siteResolver = new SiteResolver(sites);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { normalizePersonalizedRewrite } from '@sitecore-jss/sitecore-jss-nextjs';
import { Plugin } from '..';

class PersonalizePlugin implements Plugin {
exec(path: string) {
// Remove personalize rewrite segment from the path
return normalizePersonalizedRewrite(path);
}
}

export const personalizePlugin = new PersonalizePlugin();
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PersonalizeMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middlew
import { MiddlewarePlugin } from '..';
import config from 'temp/config';
import { PosResolver } from 'lib/pos-resolver';
import { siteResolver } from 'lib/site-resolver';

/**
* This is the personalize middleware plugin for Next.js.
Expand All @@ -26,7 +27,6 @@ class PersonalizePlugin implements MiddlewarePlugin {
edgeConfig: {
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
siteName: config.jssAppName,
timeout:
(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT &&
parseInt(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT)) ||
Expand All @@ -52,6 +52,8 @@ class PersonalizePlugin implements MiddlewarePlugin {
// This function resolves point of sale for cdp calls.
// Point of sale may differ by locale and middleware will use request language to get the correct value every time it's invoked
getPointOfSale: PosResolver.resolve,
// This function resolves site based on hostname
getSite: siteResolver.getByHost,
});
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getPersonalizedRewriteData, personalizeLayout } from '@sitecore-jss/sit
import { SitecorePageProps } from 'lib/page-props';

class PersonalizePlugin implements Plugin {
order = 2;
order = 3;

async exec(props: SitecorePageProps, context: GetServerSidePropsContext | GetStaticPropsContext) {
const path =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'dotenv/config';
import chalk from 'chalk';
import { constants } from '@sitecore-jss/sitecore-jss-nextjs';
import { ConfigPlugin, JssConfig } from '..';

/**
* This plugin will override the "sitecoreApiHost" config prop
* for disconnected mode (using disconnected server).
*/
class DisconnectedPlugin implements ConfigPlugin {
order = 3;

async exec(config: JssConfig) {
const disconnected = process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED;

if (!disconnected) return config;

if (process.env.FETCH_WITH === constants.FETCH_WITH.GRAPHQL) {
throw new Error(
chalk.red(
'GraphQL requests to Dictionary and Layout services are not supported in disconnected mode.'
)
);
}

const port = process.env.PORT || 3000;

return Object.assign({}, config, {
sitecoreApiHost: `http://localhost:${port}`,
});
}
}

export const disconnectedPlugin = new DisconnectedPlugin();
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ const Navigation = (): JSX.Element => {
);
};

export default Navigation;
export default Navigation;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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';

class RedirectsPlugin implements MiddlewarePlugin {
private redirectsMiddleware: RedirectsMiddleware;
Expand All @@ -11,7 +12,6 @@ class RedirectsPlugin implements MiddlewarePlugin {
this.redirectsMiddleware = new RedirectsMiddleware({
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
siteName: config.jssAppName,
// These are all the locales you support in your application.
// These should match those in your next.config.js (i18n.locales).
locales: ['en'],
Expand All @@ -22,6 +22,8 @@ class RedirectsPlugin implements MiddlewarePlugin {
// This function determines if the middleware should be turned off.
// By default it is disabled while in development mode.
disabled: () => process.env.NODE_ENV === 'development',
// This function resolves site based on hostname
getSite: siteResolver.getByHost,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class ErrorPagesPlugin implements Plugin {
const errorPagesService = new GraphQLErrorPagesService({
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
siteName: config.jssAppName,
siteName: props.site.name,
language: props.locale,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import config from 'temp/config';
import { GraphQLRobotsService } from '@sitecore-jss/sitecore-jss-nextjs';
import { siteResolver } from 'lib/site-resolver';
import config from 'temp/config';

const robotsApi = async (_req: NextApiRequest, res: NextApiResponse): Promise<void> => {
const robotsApi = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
// Ensure response is text/html
res.setHeader('Content-Type', 'text/html;charset=utf-8');

// Resolve site based on hostname
const hostName = req.headers['host']?.split(':')[0] || 'localhost';
const site = siteResolver.getByHost(hostName);

// create robots graphql service
const robotsService = new GraphQLRobotsService({
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
siteName: config.jssAppName,
siteName: site.name,
});

const robotsResult = await robotsService.fetchRobots();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AxiosResponse } from 'axios';
import type { NextApiRequest, NextApiResponse } from 'next';
import { getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs';
import { AxiosDataFetcher, GraphQLSitemapXmlService, getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs';
import { siteResolver } from 'lib/site-resolver';
import config from 'temp/config';
import { AxiosDataFetcher, GraphQLSitemapXmlService } from '@sitecore-jss/sitecore-jss-nextjs';

const ABSOLUTE_URL_REGEXP = '^(?:[a-z]+:)?//';

Expand All @@ -13,11 +13,16 @@ const sitemapApi = async (
const {
query: { id },
} = req;

// Resolve site based on hostname
const hostName = req.headers['host']?.split(':')[0] || 'localhost';
const site = siteResolver.getByHost(hostName);

// create sitemap graphql service
const sitemapXmlService = new GraphQLSitemapXmlService({
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
siteName: config.jssAppName,
siteName: site.name,
});

// if url has sitemap-{n}.xml type. The id - can be null if it's sitemap.xml request
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const jssConfig = require('./src/temp/config');
const packageConfig = require('./package.json').config;
const { getPublicUrl } = require('@sitecore-jss/sitecore-jss-nextjs');
const plugins = require('./src/temp/next-config-plugins') || {};

Expand All @@ -26,7 +25,7 @@ const nextConfig = {
locales: ['en'],
// This is the locale that will be used when visiting a non-locale
// prefixed path e.g. `/styleguide`.
defaultLocale: packageConfig.language,
defaultLocale: jssConfig.defaultLanguage,
},

// Enable React Strict Mode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"npm-run-all": "~4.1.5",
"prettier": "^2.1.2",
"ts-node": "^9.0.0",
"tsconfig-paths": "^4.1.1",
"typescript": "~4.3.5",
"yaml-loader": "^0.6.0"
},
Expand Down
Loading

0 comments on commit c4c7cd6

Please sign in to comment.