Skip to content

Commit c4c7cd6

Browse files
ambrauerilliakovalenkoart-alexeyenkoaddy-pathaniaAutomated Build
authored
[Next.js] Multi-site implementation (#1288)
* [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 <[email protected]> * [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 <[email protected]> * 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 <[email protected]> Co-authored-by: Illia Kovalenko <[email protected]> * [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 <[email protected]> * Update packages/sitecore-jss-dev-tools/src/manifest/manifest-manager.test.ts Co-authored-by: Adam Brauer <[email protected]> * 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 <[email protected]> Co-authored-by: Automated Build <[email protected]> Co-authored-by: Adam Brauer <[email protected]> Co-authored-by: Ruslan Matkovskyi <[email protected]> Co-authored-by: Ruslan Matkovskyi <[email protected]> * [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 <[email protected]> * Update packages/sitecore-jss-dev-tools/src/manifest/manifest-manager.test.ts Co-authored-by: Adam Brauer <[email protected]> * 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 <[email protected]> Co-authored-by: Automated Build <[email protected]> Co-authored-by: Adam Brauer <[email protected]> Co-authored-by: Ruslan Matkovskyi <[email protected]> Co-authored-by: Ruslan Matkovskyi <[email protected]> * 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 <[email protected]> * lint fix Co-authored-by: illiakovalenko <[email protected]> Co-authored-by: Illia Kovalenko <[email protected]> Co-authored-by: Artem Alexeyenko <[email protected]> Co-authored-by: Addy Pathania <[email protected]> Co-authored-by: Automated Build <[email protected]> Co-authored-by: Ruslan Matkovskyi <[email protected]> Co-authored-by: Ruslan Matkovskyi <[email protected]>
1 parent b0aa767 commit c4c7cd6

File tree

64 files changed

+2700
-368
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+2700
-368
lines changed

packages/create-sitecore-jss/src/templates/nextjs-multisite/multisite-sample.ts

-1
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import 'dotenv/config';
2+
import chalk from 'chalk';
3+
import { ConfigPlugin, JssConfig } from '..';
4+
import { GraphQLSiteInfoService, SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs';
5+
6+
/**
7+
* This plugin will set the "sites" config prop.
8+
* By default this will attempt to fetch site information directly from Sitecore (using the GraphQLSiteInfoService).
9+
* You could easily modify this to fetch from another source such as a static JSON file instead.
10+
*/
11+
class MultisitePlugin implements ConfigPlugin {
12+
order = 3;
13+
14+
async exec(config: JssConfig) {
15+
let sites: SiteInfo[] = [];
16+
const endpoint = process.env.GRAPH_QL_ENDPOINT || config.graphQLEndpoint;
17+
const apiKey = process.env.SITECORE_API_KEY || config.sitecoreApiKey;
18+
19+
if (!endpoint || !apiKey) {
20+
console.warn(
21+
chalk.yellow('Skipping site information fetch (missing GraphQL connection details)')
22+
);
23+
} else {
24+
console.log(`Fetching site information from ${endpoint}`);
25+
try {
26+
const siteInfoService = new GraphQLSiteInfoService({
27+
endpoint,
28+
apiKey,
29+
});
30+
sites = await siteInfoService.fetchSiteInfo();
31+
} catch (error) {
32+
console.error(chalk.red('Error fetching site information'));
33+
console.error(error);
34+
}
35+
}
36+
37+
return Object.assign({}, config, {
38+
sites: JSON.stringify(sites),
39+
});
40+
}
41+
}
42+
43+
export const multisitePlugin = new MultisitePlugin();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { normalizeSiteRewrite } from '@sitecore-jss/sitecore-jss-nextjs';
2+
import { Plugin } from '..';
3+
4+
class MultisitePlugin implements Plugin {
5+
exec(path: string) {
6+
// Remove site rewrite segment from the path
7+
return normalizeSiteRewrite(path);
8+
}
9+
}
10+
11+
export const multisitePlugin = new MultisitePlugin();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { MultisiteMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware';
3+
import { siteResolver } from 'lib/site-resolver';
4+
import { MiddlewarePlugin } from '..';
5+
6+
/**
7+
* This is the multisite middleware plugin for Next.js.
8+
* It is used to enable Sitecore multisite in Next.js.
9+
*
10+
* The `MultisiteMiddleware` will
11+
* 1. Based on provided hostname and sites information, resolve site.
12+
* 2. Rewrite the response to the specific site.
13+
* 3. Set `sc_site` cookie with site name and `x-sc-rewrite` header with rewritten path to be reused in following middlewares.
14+
*/
15+
class MultisitePlugin implements MiddlewarePlugin {
16+
private multisiteMiddleware: MultisiteMiddleware;
17+
18+
// Multisite middleware has to be executed first
19+
order = -1;
20+
21+
constructor() {
22+
this.multisiteMiddleware = new MultisiteMiddleware({
23+
// This function determines if a route should be excluded from site resolution.
24+
// Certain paths are ignored by default (e.g. files and Next.js API routes), but you may wish to exclude more.
25+
// This is an important performance consideration since Next.js Edge middleware runs on every request.
26+
excludeRoute: () => false,
27+
// This function resolves site based on hostname
28+
getSite: siteResolver.getByHost,
29+
});
30+
}
31+
32+
async exec(req: NextRequest, res?: NextResponse): Promise<NextResponse> {
33+
return this.multisiteMiddleware.getHandler()(req, res);
34+
}
35+
}
36+
37+
export const multisitePlugin = new MultisitePlugin();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { SitecorePageProps } from 'lib/page-props';
2+
import { GetServerSidePropsContext, GetStaticPropsContext } from 'next';
3+
import { getSiteRewriteData } from '@sitecore-jss/sitecore-jss-nextjs';
4+
import { Plugin } from '..';
5+
import { siteResolver } from 'lib/site-resolver';
6+
import config from 'temp/config';
7+
8+
class SitePlugin implements Plugin {
9+
order = 0;
10+
11+
async exec(props: SitecorePageProps, context: GetServerSidePropsContext | GetStaticPropsContext) {
12+
const path =
13+
context.params === undefined
14+
? '/'
15+
: Array.isArray(context.params.path)
16+
? context.params.path.join('/')
17+
: context.params.path ?? '/';
18+
19+
// Get site name (from path)
20+
const siteData = getSiteRewriteData(path, config.jssAppName);
21+
22+
// Resolve site by name
23+
props.site = siteResolver.getByName(siteData.siteName);
24+
25+
return props;
26+
}
27+
}
28+
29+
export const sitePlugin = new SitePlugin();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { SiteResolver, SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs/middleware';
2+
import config from 'temp/config';
3+
4+
/*
5+
The site resolver stores site information and is used in the app
6+
whenever site lookup is required (e.g. by name in page props factory
7+
or by host in Next.js middleware).
8+
*/
9+
10+
// Grab our configured sites
11+
const sites = JSON.parse(config.sites) as SiteInfo[];
12+
13+
// Then add fallback site with default values
14+
sites.push({
15+
name: config.jssAppName,
16+
language: config.defaultLanguage,
17+
hostName: '*',
18+
});
19+
20+
/** SiteResolver singleton */
21+
export const siteResolver = new SiteResolver(sites);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { normalizePersonalizedRewrite } from '@sitecore-jss/sitecore-jss-nextjs';
2+
import { Plugin } from '..';
3+
4+
class PersonalizePlugin implements Plugin {
5+
exec(path: string) {
6+
// Remove personalize rewrite segment from the path
7+
return normalizePersonalizedRewrite(path);
8+
}
9+
}
10+
11+
export const personalizePlugin = new PersonalizePlugin();

packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { PersonalizeMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middlew
33
import { MiddlewarePlugin } from '..';
44
import config from 'temp/config';
55
import { PosResolver } from 'lib/pos-resolver';
6+
import { siteResolver } from 'lib/site-resolver';
67

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

packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/page-props-factory/extract-path.ts

-23
This file was deleted.

packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/page-props-factory/plugins/personalize.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getPersonalizedRewriteData, personalizeLayout } from '@sitecore-jss/sit
44
import { SitecorePageProps } from 'lib/page-props';
55

66
class PersonalizePlugin implements Plugin {
7-
order = 2;
7+
order = 3;
88

99
async exec(props: SitecorePageProps, context: GetServerSidePropsContext | GetStaticPropsContext) {
1010
const path =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'dotenv/config';
2+
import chalk from 'chalk';
3+
import { constants } from '@sitecore-jss/sitecore-jss-nextjs';
4+
import { ConfigPlugin, JssConfig } from '..';
5+
6+
/**
7+
* This plugin will override the "sitecoreApiHost" config prop
8+
* for disconnected mode (using disconnected server).
9+
*/
10+
class DisconnectedPlugin implements ConfigPlugin {
11+
order = 3;
12+
13+
async exec(config: JssConfig) {
14+
const disconnected = process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED;
15+
16+
if (!disconnected) return config;
17+
18+
if (process.env.FETCH_WITH === constants.FETCH_WITH.GRAPHQL) {
19+
throw new Error(
20+
chalk.red(
21+
'GraphQL requests to Dictionary and Layout services are not supported in disconnected mode.'
22+
)
23+
);
24+
}
25+
26+
const port = process.env.PORT || 3000;
27+
28+
return Object.assign({}, config, {
29+
sitecoreApiHost: `http://localhost:${port}`,
30+
});
31+
}
32+
}
33+
34+
export const disconnectedPlugin = new DisconnectedPlugin();

packages/create-sitecore-jss/src/templates/nextjs-styleguide/src/Navigation.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ const Navigation = (): JSX.Element => {
3838
);
3939
};
4040

41-
export default Navigation;
41+
export default Navigation;

packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/middleware/plugins/redirects.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
22
import { RedirectsMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware';
33
import config from 'temp/config';
44
import { MiddlewarePlugin } from '..';
5+
import { siteResolver } from 'lib/site-resolver';
56

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

packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/page-props-factory/plugins/error-pages.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class ErrorPagesPlugin implements Plugin {
1212
const errorPagesService = new GraphQLErrorPagesService({
1313
endpoint: config.graphQLEndpoint,
1414
apiKey: config.sitecoreApiKey,
15-
siteName: config.jssAppName,
15+
siteName: props.site.name,
1616
language: props.locale,
1717
});
1818

packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/api/robots.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import type { NextApiRequest, NextApiResponse } from 'next';
2-
import config from 'temp/config';
32
import { GraphQLRobotsService } from '@sitecore-jss/sitecore-jss-nextjs';
3+
import { siteResolver } from 'lib/site-resolver';
4+
import config from 'temp/config';
45

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

10+
// Resolve site based on hostname
11+
const hostName = req.headers['host']?.split(':')[0] || 'localhost';
12+
const site = siteResolver.getByHost(hostName);
13+
914
// create robots graphql service
1015
const robotsService = new GraphQLRobotsService({
1116
endpoint: config.graphQLEndpoint,
1217
apiKey: config.sitecoreApiKey,
13-
siteName: config.jssAppName,
18+
siteName: site.name,
1419
});
1520

1621
const robotsResult = await robotsService.fetchRobots();

packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/api/sitemap.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { AxiosResponse } from 'axios';
22
import type { NextApiRequest, NextApiResponse } from 'next';
3-
import { getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs';
3+
import { AxiosDataFetcher, GraphQLSitemapXmlService, getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs';
4+
import { siteResolver } from 'lib/site-resolver';
45
import config from 'temp/config';
5-
import { AxiosDataFetcher, GraphQLSitemapXmlService } from '@sitecore-jss/sitecore-jss-nextjs';
66

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

@@ -13,11 +13,16 @@ const sitemapApi = async (
1313
const {
1414
query: { id },
1515
} = req;
16+
17+
// Resolve site based on hostname
18+
const hostName = req.headers['host']?.split(':')[0] || 'localhost';
19+
const site = siteResolver.getByHost(hostName);
20+
1621
// create sitemap graphql service
1722
const sitemapXmlService = new GraphQLSitemapXmlService({
1823
endpoint: config.graphQLEndpoint,
1924
apiKey: config.sitecoreApiKey,
20-
siteName: config.jssAppName,
25+
siteName: site.name,
2126
});
2227

2328
// if url has sitemap-{n}.xml type. The id - can be null if it's sitemap.xml request

packages/create-sitecore-jss/src/templates/nextjs/next.config.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const jssConfig = require('./src/temp/config');
2-
const packageConfig = require('./package.json').config;
32
const { getPublicUrl } = require('@sitecore-jss/sitecore-jss-nextjs');
43
const plugins = require('./src/temp/next-config-plugins') || {};
54

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

3231
// Enable React Strict Mode

packages/create-sitecore-jss/src/templates/nextjs/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"npm-run-all": "~4.1.5",
6868
"prettier": "^2.1.2",
6969
"ts-node": "^9.0.0",
70+
"tsconfig-paths": "^4.1.1",
7071
"typescript": "~4.3.5",
7172
"yaml-loader": "^0.6.0"
7273
},

0 commit comments

Comments
 (0)