diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8ccf8c45..6e0b941ef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Our versioning strategy is as follows: * `[sitecore-jss-react]` `[sitecore-jss-nextjs]` FEaaS component will render 'staged' variant for editing and preview and 'published' variant for live site by default, unless variant is overriden via rendering parameters. ([#1433](https://github.com/Sitecore/jss/pull/1433)) * `[templates/nextjs]` `[templates/angular]` `[templates/react]` `[templates/vue]` Pre-push hook for lint check ([#1427](https://github.com/Sitecore/jss/pull/1427)) ([#1442](https://github.com/Sitecore/jss/pull/1442)) ([#1444](https://github.com/Sitecore/jss/pull/1444)) +* `[sitecore-jss-nextjs] Add a new handling for token $siteLang(context site language) in middleware redirect ([#1454](https://github.com/Sitecore/jss/pull/1454)) ### 🧹 Chores diff --git a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts index 7e99872b72..4aea83066a 100644 --- a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts +++ b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts @@ -26,6 +26,10 @@ describe('RedirectsMiddleware', () => { const referrer = 'http://localhost:3000'; const hostname = 'foo.net'; const siteName = 'nextjs-app'; + const sitesFromConfigFile = [ + { name: 'basicSite', hostName: 'localhost', language: 'en' }, + { name: 'nextjs-app', hostName: '*', language: 'da' }, + ]; const createRequest = (props: any = {}) => { const req = { @@ -98,6 +102,7 @@ describe('RedirectsMiddleware', () => { } = {} ) => { class MockSiteResolver extends SiteResolver { + sites = sitesFromConfigFile; getByName = sinon.stub().callsFake((siteName: string) => ({ name: siteName, language: props.language || '', @@ -106,7 +111,7 @@ describe('RedirectsMiddleware', () => { getByHost = sinon.stub().callsFake((hostName: string) => ({ name: siteName, - language: props.language || '', + language: props.language || 'da', hostName, })); } @@ -640,7 +645,7 @@ describe('RedirectsMiddleware', () => { pattern: 'not-found', target: 'http://localhost:3000/found', redirectType: REDIRECT_TYPE_302, - isQueryStringPreserved: true, + isQueryStringPreserved: false, locale: 'en', }); @@ -668,6 +673,66 @@ describe('RedirectsMiddleware', () => { nextRedirectStub.restore(); }); + it('should redirect uses token $siteLang in target url', async () => { + const setCookies = () => {}; + const res = createResponse({ + url: 'http://localhost:3000/da/found', + status: 301, + setCookies, + }); + const nextRedirectStub = sinon.stub(NextResponse, 'redirect').callsFake((url, status) => { + return ({ + url, + status, + cookies: { set: setCookies }, + headers: res.headers, + } as unknown) as NextResponse; + }); + const req = createRequest({ + nextUrl: { + pathname: '/not-found', + search: 'abc=def', + href: 'http://localhost:3000/not-found', + locale: 'en', + clone() { + return Object.assign({}, req.nextUrl); + }, + }, + }); + + const { middleware, fetchRedirects, siteResolver } = createMiddleware({ + pattern: '/not-found/', + target: 'http://localhost:3000/$siteLang/found', + redirectType: REDIRECT_TYPE_301, + isQueryStringPreserved: false, + locale: 'en', + sites: sitesFromConfigFile, + }); + + const finalRes = await middleware.getHandler()(req); + + validateDebugLog('redirects middleware start: %o', { + hostname: 'foo.net', + language: 'en', + pathname: '/not-found', + }); + + validateDebugLog('redirects middleware end: %o', { + headers: {}, + redirected: undefined, + status: 301, + url: 'http://localhost:3000/da/found', + }); + + expect(siteResolver.getByHost).to.be.calledWith(hostname); + // eslint-disable-next-line no-unused-expressions + expect(fetchRedirects.called).to.be.true; + expect(finalRes).to.deep.equal(res); + expect(finalRes.status).to.equal(res.status); + + nextRedirectStub.restore(); + }); + it('should return default response if no redirect type defined', async () => { const setCookies = () => {}; const res = createResponse({ diff --git a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts index 4a4e132b7c..1b843bc87a 100644 --- a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts +++ b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts @@ -12,6 +12,8 @@ import { import { debug } from '@sitecore-jss/sitecore-jss'; import { MiddlewareBase, MiddlewareBaseConfig } from './middleware'; +const REGEXP_CONTEXT_SITE_LANG = new RegExp(/\$siteLang/, 'gi'); + /** * extended RedirectsMiddlewareConfig config type for RedirectsMiddleware */ @@ -44,11 +46,19 @@ export class RedirectsMiddleware extends MiddlewareBase { } /** - * Gets the Next.js API route handler + * Gets the Next.js middleware handler with error handling * @returns route handler */ public getHandler(): (req: NextRequest, res?: NextResponse) => Promise { - return this.handler; + return async (req, res) => { + try { + return await this.handler(req, res); + } catch (error) { + console.log('Redirect middleware failed:'); + console.log(error); + return res || NextResponse.next(); + } + }; } private handler = async (req: NextRequest, res?: NextResponse): Promise => { @@ -86,6 +96,14 @@ export class RedirectsMiddleware extends MiddlewareBase { return res || NextResponse.next(); } + // Find context site language and replace token + if (REGEXP_CONTEXT_SITE_LANG.test(existsRedirect.target)) { + existsRedirect.target = existsRedirect.target.replace( + REGEXP_CONTEXT_SITE_LANG, + site.language + ); + } + const url = req.nextUrl.clone(); const absoluteUrlRegex = new RegExp('^(?:[a-z]+:)?//', 'i'); if (absoluteUrlRegex.test(existsRedirect.target)) { @@ -145,15 +163,14 @@ export class RedirectsMiddleware extends MiddlewareBase { siteName: string ): Promise { const redirects = await this.redirectsService.fetchRedirects(siteName); - const tragetURL = `${req.nextUrl.pathname}`.toLowerCase(); - const targetQS = `?${req.nextUrl.search || ''}`.toLowerCase(); + const tragetURL = req.nextUrl.pathname; + const targetQS = `?${req.nextUrl.search || ''}`; return redirects.length ? redirects.find((redirect: RedirectInfo) => { const pattern = `/^/${redirect.pattern - .toLowerCase() .replace(/^\/|\/$/g, '') - .replace(/^\^|\$$/g, '')}$/`; + .replace(/^\^|\$$/g, '')}$/gi`; return ( (regexParser(pattern).test(tragetURL) ||