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] Multi-site implementation #1288

Merged
merged 16 commits into from
Jan 12, 2023
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

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