diff --git a/docs/data/routes/docs/nextjs/deploying-to-production/vercel/en.md b/docs/data/routes/docs/nextjs/deploying-to-production/vercel/en.md index ac2a89b5f6..f2cb2a3cf9 100644 --- a/docs/data/routes/docs/nextjs/deploying-to-production/vercel/en.md +++ b/docs/data/routes/docs/nextjs/deploying-to-production/vercel/en.md @@ -13,9 +13,10 @@ This guide will demonstrate deploying the Next.js sample app that's generated by 1. Execute the steps provided in the Next.js guide for [Getting Started](https://nextjs.org/docs/deployment#getting-started) with deployment. 2. Add [environment variables](https://nextjs.org/docs/basic-features/environment-variables#environment-variables-on-vercel) to Vercel or modify `.env` file. We recommend to use environment variables in Vercel: * `PUBLIC_URL` - your Vercel deployment URL. - * `SITECORE_API_KEY` - your Sitecore API key is needed to build the app. - * `SITECORE_API_HOST` - your Sitecore API hostname. + * `SITECORE_API_KEY` - your Sitecore API key. For Sitecore XM, the `SITECORE_API_KEY` will be your Sitecore SSC API key. For Sitecore Experience Edge, this will be the API key provisioned here. + * `SITECORE_API_HOST` - your Sitecore API hostname. The `SITECORE_API_HOST` is not required for Sitecore Experience Edge. * `JSS_EDITING_SECRET` - your secret token. The `JSS_EDITING_SECRET` is optional for deployments but necessary if you want to use the Experience Editor with your Next.js Vercel deployment. Read about [connecting your Next.js application to the Experience Editor](/docs/nextjs/experience-editor/walkthrough). + * `GRAPH_QL_ENDPOINT` - your GraphQL endpoint. The `GRAPH_QL_ENDPOINT` is required for Sitecore Experience Edge. For Sitecore XM, this is typically optional. By default, the endpoint is calculated using the resolved Sitecore API hostname + the `graphQLEndpointPath` defined in your `package.json`. 3. Push the changes to your Git provider. ## `publish:end` webhook invocation diff --git a/packages/sitecore-jss-nextjs/src/services/graphql-sitemap-service.test.ts b/packages/sitecore-jss-nextjs/src/services/graphql-sitemap-service.test.ts index 5fb59bd5c8..5a2955675f 100644 --- a/packages/sitecore-jss-nextjs/src/services/graphql-sitemap-service.test.ts +++ b/packages/sitecore-jss-nextjs/src/services/graphql-sitemap-service.test.ts @@ -4,12 +4,17 @@ import { GraphQLSitemapService } from './graphql-sitemap-service'; describe('GraphQLSitemapService', () => { const ROOT_ITEM = '/sitecore/next/home'; - const graphQLLayoutService = new GraphQLSitemapService({ + const graphQLSitemapService = new GraphQLSitemapService({ endpoint: 'http://jssnextweb/graphql', + apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA', }); const mockRootItemIdRequest = () => { - nock('http://jssnextweb') + nock('http://jssnextweb', { + reqheaders: { + sc_apikey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA', + }, + }) .post('/graphql') .reply(200, { data: { @@ -21,7 +26,11 @@ describe('GraphQLSitemapService', () => { }; const mockPathsRequest = (results: { url: { path: string } }[]) => { - nock('http://jssnextweb') + nock('http://jssnextweb', { + reqheaders: { + sc_apikey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA', + }, + }) .post('/graphql') .reply(200, { data: { @@ -55,7 +64,7 @@ describe('GraphQLSitemapService', () => { }, ]); - const sitemap = await graphQLLayoutService.fetchSSGSitemap(['ua'], ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchSSGSitemap(['ua'], ROOT_ITEM); expect(sitemap).to.deep.equal([ { @@ -118,7 +127,7 @@ describe('GraphQLSitemapService', () => { }, ]); - const sitemap = await graphQLLayoutService.fetchSSGSitemap(['ua', 'da-DK'], ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchSSGSitemap(['ua', 'da-DK'], ROOT_ITEM); expect(sitemap).to.deep.equal([ { @@ -177,7 +186,7 @@ describe('GraphQLSitemapService', () => { mockPathsRequest([]); - const sitemap = await graphQLLayoutService.fetchSSGSitemap(['ua'], ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchSSGSitemap(['ua'], ROOT_ITEM); expect(sitemap).to.deep.equal([]); }); @@ -185,7 +194,7 @@ describe('GraphQLSitemapService', () => { it('should fetch sitemap if locales are not provided', async () => { mockRootItemIdRequest(); - const sitemap = await graphQLLayoutService.fetchSSGSitemap([], ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchSSGSitemap([], ROOT_ITEM); expect(sitemap).to.deep.equal([]); }); @@ -197,7 +206,7 @@ describe('GraphQLSitemapService', () => { data: null, }); - const sitemap = await graphQLLayoutService.fetchSSGSitemap([], ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchSSGSitemap([], ROOT_ITEM); expect(sitemap).to.deep.equal([]); }); @@ -209,7 +218,7 @@ describe('GraphQLSitemapService', () => { error: 'whoops', }); - const sitemap = await graphQLLayoutService.fetchSSGSitemap(['ua'], ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchSSGSitemap(['ua'], ROOT_ITEM); expect(sitemap).to.deep.equal([]); }); @@ -221,7 +230,7 @@ describe('GraphQLSitemapService', () => { .post('/graphql') .reply(401, 'whoops'); - const sitemap = await graphQLLayoutService.fetchSSGSitemap(['ua'], ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchSSGSitemap(['ua'], ROOT_ITEM); expect(sitemap).to.deep.equal([]); }); @@ -246,7 +255,7 @@ describe('GraphQLSitemapService', () => { }, ]); - const sitemap = await graphQLLayoutService.fetchExportSitemap('ua', ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchExportSitemap('ua', ROOT_ITEM); expect(sitemap).to.deep.equal([ { @@ -277,7 +286,7 @@ describe('GraphQLSitemapService', () => { mockPathsRequest([]); - const sitemap = await graphQLLayoutService.fetchExportSitemap('ua', ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchExportSitemap('ua', ROOT_ITEM); expect(sitemap).to.deep.equal([]); }); @@ -289,7 +298,7 @@ describe('GraphQLSitemapService', () => { data: null, }); - const sitemap = await graphQLLayoutService.fetchSSGSitemap([], ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchSSGSitemap([], ROOT_ITEM); expect(sitemap).to.deep.equal([]); }); @@ -301,7 +310,7 @@ describe('GraphQLSitemapService', () => { error: 'whoops', }); - const sitemap = await graphQLLayoutService.fetchSSGSitemap(['ua'], ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchSSGSitemap(['ua'], ROOT_ITEM); expect(sitemap).to.deep.equal([]); }); @@ -313,7 +322,7 @@ describe('GraphQLSitemapService', () => { .post('/graphql') .reply(401, 'whoops'); - const sitemap = await graphQLLayoutService.fetchSSGSitemap(['ua'], ROOT_ITEM); + const sitemap = await graphQLSitemapService.fetchSSGSitemap(['ua'], ROOT_ITEM); expect(sitemap).to.deep.equal([]); }); diff --git a/packages/sitecore-jss-nextjs/src/services/graphql-sitemap-service.ts b/packages/sitecore-jss-nextjs/src/services/graphql-sitemap-service.ts index a48e93c22d..084c420d48 100644 --- a/packages/sitecore-jss-nextjs/src/services/graphql-sitemap-service.ts +++ b/packages/sitecore-jss-nextjs/src/services/graphql-sitemap-service.ts @@ -7,6 +7,10 @@ export type GraphQLSitemapServiceConfig = { * Your Graphql endpoint */ endpoint: string; + /** + * The API key to use for authentication. + */ + apiKey: string; }; type SearchResult = { @@ -175,9 +179,9 @@ export class GraphQLSitemapService { * Returns new graphql client instance */ private createClient(): GraphQLRequestClient { - const { endpoint } = this.config; + const { endpoint, apiKey } = this.config; - return new GraphQLRequestClient(endpoint); + return new GraphQLRequestClient(endpoint, apiKey); } /** diff --git a/packages/sitecore-jss/src/graphql-request-client.test.ts b/packages/sitecore-jss/src/graphql-request-client.test.ts index 6641305493..c51102f1ca 100644 --- a/packages/sitecore-jss/src/graphql-request-client.test.ts +++ b/packages/sitecore-jss/src/graphql-request-client.test.ts @@ -3,11 +3,11 @@ import nock from 'nock'; import { GraphQLRequestClient } from './graphql-request-client'; describe('GraphQLRequestClient', () => { - const ENDPOINT = 'http://jssnextweb/graphql'; + const endpoint = 'http://jssnextweb/graphql'; - const graphQLClient = new GraphQLRequestClient(ENDPOINT); + afterEach(nock.cleanAll); - const mockGraphQLRequest = () => { + it('should execute graphql request', async () => { nock('http://jssnextweb') .post('/graphql') .reply(200, { @@ -15,15 +15,28 @@ describe('GraphQLRequestClient', () => { result: 'Hello world...', }, }); - }; - - afterEach(nock.cleanAll); - - it('should execute graphql request', async () => { - mockGraphQLRequest(); + const graphQLClient = new GraphQLRequestClient(endpoint); const data = await graphQLClient.request('test'); expect(data).to.deep.equal({ result: 'Hello world...' }); }); + + it('should send sc_apikey header', async () => { + const apiKey = 'cjhNRWNVOHRFTklwUjhYa0RSTnBhSStIam1mNE1KN1pyeW13c3FnRVExTT18bXRzdC1kLTAxOQ=='; + nock('http://jssnextweb', { + reqheaders: { + sc_apikey: apiKey, + }, + }) + .post('/graphql') + .reply(200, { + data: { + result: 'Hello world...', + }, + }); + + const graphQLClient = new GraphQLRequestClient(endpoint, apiKey); + await graphQLClient.request('test'); + }); }); diff --git a/packages/sitecore-jss/src/graphql-request-client.ts b/packages/sitecore-jss/src/graphql-request-client.ts index 3969da4fc7..a356f306bd 100644 --- a/packages/sitecore-jss/src/graphql-request-client.ts +++ b/packages/sitecore-jss/src/graphql-request-client.ts @@ -4,9 +4,10 @@ import chalk from 'chalk'; export class GraphQLRequestClient { /** * Provides ability to execute graphql query using given `endpoint` - * @param {string} endpoint Your Graphql endpoint + * @param {string} endpoint The Graphql endpoint + * @param {string} [apiKey] The API key to use for authentication. This will be added as an 'sc_apikey' header. */ - constructor(private endpoint: string) {} + constructor(private endpoint: string, private apiKey?: string) {} /** * Execute graphql request @@ -14,11 +15,14 @@ export class GraphQLRequestClient { * @param {Object} variables graphql variables */ async request(query: string, variables?: { [key: string]: unknown }): Promise { - const client = new GraphQLClient(this.endpoint); + const client = new GraphQLClient( + this.endpoint, + this.apiKey ? { headers: { sc_apikey: this.apiKey } } : undefined + ); const onError = (error: unknown) => { console.error( chalk.red(` - Error occurred while fetching attempting to fetch graphQL data. + Error occurred while attempting to fetch graphQL data. Endpoint: ${this.endpoint} Query: ${query} Variables: ${JSON.stringify(variables, null, 2)} diff --git a/packages/sitecore-jss/src/i18n/graphql-dictionary-service.test.ts b/packages/sitecore-jss/src/i18n/graphql-dictionary-service.test.ts index 315e6359bf..dfe40e605b 100644 --- a/packages/sitecore-jss/src/i18n/graphql-dictionary-service.test.ts +++ b/packages/sitecore-jss/src/i18n/graphql-dictionary-service.test.ts @@ -8,42 +8,73 @@ import dictionaryQueryResponse from './mockDictionaryQueryResponse.json'; describe('GraphQLDictionaryService', () => { const endpoint = 'http://site'; const siteName = 'site-name'; + const apiKey = '0FBFF61E-267A-43E3-9252-B77E71CEE4BA'; afterEach(() => { nock.cleanAll(); }); it('should fetch app root', async () => { - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', /GetSiteRoot/gi) .reply(200, siteRootQueryResponse); - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', /DictionarySearch/gi) .reply(200, dictionaryQueryResponse); - const service = new GraphQLDictionaryService({ endpoint, siteName, cacheEnabled: false }); + const service = new GraphQLDictionaryService({ + endpoint, + apiKey, + siteName, + cacheEnabled: false, + }); await service.fetchDictionaryData('en'); expect(service.options.rootItemId).to.equal('GUIDGUIDGUID'); }); it('should fetch dictionary phrases', async () => { - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', /GetSiteRoot/gi) .reply(200, siteRootQueryResponse); - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', /DictionarySearch/gi) .reply(200, dictionaryQueryResponse); - const service = new GraphQLDictionaryService({ endpoint, siteName, cacheEnabled: false }); + const service = new GraphQLDictionaryService({ + endpoint, + apiKey, + siteName, + cacheEnabled: false, + }); const result = await service.fetchDictionaryData('en'); expect(result.foo).to.equal('foo'); expect(result.bar).to.equal('bar'); }); it('should throw error if no app root found', async () => { - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', /GetSiteRoot/gi) .reply(200, { data: { @@ -55,11 +86,20 @@ describe('GraphQLDictionaryService', () => { }, }); - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', /DictionarySearch/gi) .reply(200, dictionaryQueryResponse); - const service = new GraphQLDictionaryService({ endpoint, siteName, cacheEnabled: false }); + const service = new GraphQLDictionaryService({ + endpoint, + apiKey, + siteName, + cacheEnabled: false, + }); await service.fetchDictionaryData('en').catch((error) => { expect(error.message).to.equal('Error fetching Sitecore site root item'); }); @@ -67,16 +107,24 @@ describe('GraphQLDictionaryService', () => { // TODO: there is a known issue with mcache it('should use cache', async () => { - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', /GetSiteRoot/gi) .reply(200, siteRootQueryResponse); - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', /DictionarySearch/gi) .times(2) .reply(200, dictionaryQueryResponse); - const service = new GraphQLDictionaryService({ endpoint, siteName }); + const service = new GraphQLDictionaryService({ endpoint, apiKey, siteName }); // call fetch twice, and use nock to see if more than 1 request was actually made await service.fetchDictionaryData('en'); @@ -87,12 +135,17 @@ describe('GraphQLDictionaryService', () => { it('should use a custom rootItemId, if provided', async () => { const customRootId = 'cats'; - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', (body) => body.variables.rootItemId === customRootId) .reply(200, dictionaryQueryResponse); const service = new GraphQLDictionaryService({ endpoint, + apiKey, siteName, cacheEnabled: false, rootItemId: customRootId, @@ -103,16 +156,25 @@ describe('GraphQLDictionaryService', () => { it('should use a custom pageSize, if provided', async () => { const customPageSize = 2; - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', /GetSiteRoot/gi) .reply(200, siteRootQueryResponse); - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', (body) => body.variables.pageSize === customPageSize) .reply(200, dictionaryQueryResponse); const service = new GraphQLDictionaryService({ endpoint, + apiKey, siteName, cacheEnabled: false, pageSize: customPageSize, @@ -123,12 +185,17 @@ describe('GraphQLDictionaryService', () => { it('should use lowercase GUIDs in GraphQL search', async () => { const customRootId = '{FOO-BAR-123}'; - nock(endpoint) + nock(endpoint, { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/', (body) => body.variables.rootItemId === '{foo-bar-123}') .reply(200, dictionaryQueryResponse); const service = new GraphQLDictionaryService({ endpoint, + apiKey, siteName, cacheEnabled: false, rootItemId: customRootId, diff --git a/packages/sitecore-jss/src/i18n/graphql-dictionary-service.ts b/packages/sitecore-jss/src/i18n/graphql-dictionary-service.ts index b3ebb95103..ea370207ef 100644 --- a/packages/sitecore-jss/src/i18n/graphql-dictionary-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-dictionary-service.ts @@ -69,6 +69,11 @@ export interface GraphQLDictionaryServiceConfig extends CacheOptions { * The name of the current Sitecore site. */ siteName: string; + + /** + * The API key to use for authentication. + */ + apiKey: string; } /** @@ -151,7 +156,7 @@ export class GraphQLDictionaryService extends DictionaryServiceBase { return cachedValue; } - const client = new GraphQLRequestClient(this.options.endpoint); + const client = new GraphQLRequestClient(this.options.endpoint, this.options.apiKey); if (!this.options.rootItemId) { this.options.rootItemId = await getSiteRoot(client, this.options.siteName, language); } @@ -199,7 +204,7 @@ export class GraphQLDictionaryService extends DictionaryServiceBase { // TODO: Move to shared area and reuse for sitemap service (Anastasiya, March 2021) const siteRootQuery = ` -query getSiteRoot($jssAppTemplateId: String!, $siteName: String!, $language: String) +query getSiteRoot($jssAppTemplateId: String!, $siteName: String!, $language: String!) { layout(site: $siteName, routePath: "/", language: $language) { homePage: item { diff --git a/packages/sitecore-jss/src/layout-service.test.ts b/packages/sitecore-jss/src/layout-service.test.ts index 9fa887a44a..101140b709 100644 --- a/packages/sitecore-jss/src/layout-service.test.ts +++ b/packages/sitecore-jss/src/layout-service.test.ts @@ -292,12 +292,18 @@ describe('LayoutService', () => { }); describe('GraphQLLayoutService', () => { + const apiKey = '0FBFF61E-267A-43E3-9252-B77E71CEE4BA'; + afterEach(() => { nock.cleanAll(); }); it('should fetch layout data', async () => { - nock('http://sctest') + nock('http://sctest', { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/graphql', (body) => { return ( body.query.replace(/\n|\s/g, '') === @@ -327,6 +333,7 @@ describe('LayoutService', () => { const service = new GraphQLLayoutService({ endpoint: 'http://sctest/graphql', + apiKey: apiKey, siteName: 'supersite', }); @@ -347,7 +354,11 @@ describe('LayoutService', () => { }); it('should fetch layout data if locale is not provided', async () => { - nock('http://sctest') + nock('http://sctest', { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/graphql', (body) => { return ( body.query.replace(/\n|\s/g, '') === @@ -377,6 +388,7 @@ describe('LayoutService', () => { const service = new GraphQLLayoutService({ endpoint: 'http://sctest/graphql', + apiKey: apiKey, siteName: 'supersite', }); @@ -397,7 +409,11 @@ describe('LayoutService', () => { }); it('should fetch layout data using custom layout query', async () => { - nock('http://sctest') + nock('http://sctest', { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/graphql', (body) => { return ( body.query.replace(/\n|\s/g, '') === @@ -427,6 +443,7 @@ describe('LayoutService', () => { const service = new GraphQLLayoutService({ endpoint: 'http://sctest/graphql', + apiKey: apiKey, siteName: 'supersite', formatLayoutQuery: (siteName, itemPath, locale) => `layout111(site:"${siteName}",route:"${itemPath}",language:"${locale || 'en'}")`, @@ -449,7 +466,11 @@ describe('LayoutService', () => { }); it('should return error', async () => { - nock('http://sctest') + nock('http://sctest', { + reqheaders: { + sc_apikey: apiKey, + }, + }) .post('/graphql') .reply(401, { error: 'whoops', @@ -457,6 +478,7 @@ describe('LayoutService', () => { const service = new GraphQLLayoutService({ endpoint: 'http://sctest/graphql', + apiKey: apiKey, siteName: 'supersite', }); diff --git a/packages/sitecore-jss/src/layout-service.ts b/packages/sitecore-jss/src/layout-service.ts index ca5d5d9e33..41038a8a69 100644 --- a/packages/sitecore-jss/src/layout-service.ts +++ b/packages/sitecore-jss/src/layout-service.ts @@ -68,6 +68,10 @@ export type GraphQLLayoutServiceConfig = { * The JSS application name */ siteName: string; + /** + * The API key to use for authentication + */ + apiKey: string; /** * Override default layout query * @param {string} siteName @@ -253,9 +257,9 @@ export class GraphQLLayoutService implements LayoutService { * Returns new graphql client instance */ private createClient(): GraphQLRequestClient { - const { endpoint } = this.serviceConfig; + const { endpoint, apiKey } = this.serviceConfig; - return new GraphQLRequestClient(endpoint); + return new GraphQLRequestClient(endpoint, apiKey); } /** diff --git a/samples/nextjs/.env b/samples/nextjs/.env index 0cb25b2a26..6f4c3b1434 100644 --- a/samples/nextjs/.env +++ b/samples/nextjs/.env @@ -29,4 +29,9 @@ SITECORE_API_KEY= # not exist when building locally (if you've never run `jss setup`), or when building # in a higher environment (since `scjssconfig.json` is ignored from source control). # In this case, use this environment variable to provide the value at build time. -SITECORE_API_HOST= \ No newline at end of file +SITECORE_API_HOST= + +# Your GraphQL Edge endpoint. This is required for Sitecore Experience Edge. +# For Sitecore XM, this is typically optional. By default, the endpoint is calculated using +# the resolved Sitecore API hostname + the `graphQLEndpointPath` defined in your `package.json`. +GRAPH_QL_ENDPOINT= \ No newline at end of file diff --git a/samples/nextjs/scripts/generate-config.ts b/samples/nextjs/scripts/generate-config.ts index 5ae3d7cddc..0ccf89dd53 100644 --- a/samples/nextjs/scripts/generate-config.ts +++ b/samples/nextjs/scripts/generate-config.ts @@ -24,19 +24,15 @@ export function generateConfig(configOverrides?: { [key: string]: string }): voi const scjssConfig = transformScJssConfig(); const packageJson = transformPackageConfig(); - // optional: - // do any other dynamic config source (e.g. environment-specific config files) - // Object.assign merges the objects in order, so the - // package.json config can override the calculated config, - // scjssconfig.json overrides it, - // and finally config passed in the configOverrides param wins. + // Object.assign merges the objects in order, so config overrides are performed as: + // default config <-- scjssconfig.json <-- package.json <-- configOverrides + // Optional: add any other dynamic config source (e.g. environment-specific config files). const config = Object.assign(defaultConfig, scjssConfig, packageJson, configOverrides); // The GraphQL endpoint is an example of making a _computed_ config setting // based on other config settings. const computedConfig: { [key: string]: string } = {}; - computedConfig.graphQLEndpoint = - '`${config.sitecoreApiHost}${config.graphQLEndpointPath}?sc_apikey=${config.sitecoreApiKey}`'; + computedConfig.graphQLEndpoint = '`${config.sitecoreApiHost}${config.graphQLEndpointPath}`'; let configText = `/* eslint-disable */ // Do not edit this file, it is auto-generated at build time! @@ -47,9 +43,11 @@ const config = {};\n`; Object.keys(config).forEach((prop) => { configText += `config.${prop} = process.env.${constantCase(prop)} || "${config[prop]}",\n`; }); - // Set computed values + // Set computed values, allowing override with environment variables Object.keys(computedConfig).forEach((prop) => { - configText += `config.${prop} = ${computedConfig[prop]};\n`; + configText += `config.${prop} = process.env.${constantCase(prop)} || ${ + computedConfig[prop] + };\n`; }); configText += `module.exports = config;`; @@ -59,8 +57,8 @@ const config = {};\n`; } function transformScJssConfig() { - // scjssconfig.json may not exist if you've never run setup - // so if it doesn't we substitute a fake object + // scjssconfig.json may not exist if you've never run `jss setup` (development) + // or are depending on environment variables instead (production). let config; try { // eslint-disable-next-line global-require diff --git a/samples/nextjs/src/lib/GraphQLClientFactory.ts b/samples/nextjs/src/lib/GraphQLClientFactory.ts index f002f465ef..eb16d41096 100644 --- a/samples/nextjs/src/lib/GraphQLClientFactory.ts +++ b/samples/nextjs/src/lib/GraphQLClientFactory.ts @@ -50,6 +50,9 @@ export default function GraphQLClientFactory( credentials: 'include', headers: { connection: 'keep-alive', + // Send the API key on the 'sc_apikey' header for authentication. + // This header is used for both Headless Services GraphQL endpoint and Sitecore Experience Edge. + sc_apikey: config.sitecoreApiKey, }, }) ); diff --git a/samples/nextjs/src/lib/dictionary-service-factory.graphql.ts b/samples/nextjs/src/lib/dictionary-service-factory.graphql.ts index b70d720777..88af46ccfa 100644 --- a/samples/nextjs/src/lib/dictionary-service-factory.graphql.ts +++ b/samples/nextjs/src/lib/dictionary-service-factory.graphql.ts @@ -5,6 +5,7 @@ export class DictionaryServiceFactory { create(): DictionaryService { return new GraphQLDictionaryService({ endpoint: config.graphQLEndpoint, + apiKey: config.sitecoreApiKey, siteName: config.jssAppName, }); } diff --git a/samples/nextjs/src/lib/layout-service-factory.graphql.ts b/samples/nextjs/src/lib/layout-service-factory.graphql.ts index bf8610c841..e6914e840d 100644 --- a/samples/nextjs/src/lib/layout-service-factory.graphql.ts +++ b/samples/nextjs/src/lib/layout-service-factory.graphql.ts @@ -1,8 +1,13 @@ -import { LayoutService } from '@sitecore-jss/sitecore-jss-nextjs'; +import { LayoutService, GraphQLLayoutService } from '@sitecore-jss/sitecore-jss-nextjs'; +import config from 'temp/config'; export class LayoutServiceFactory { create(): LayoutService { - throw new Error('GraphQLLayoutService not implemented!'); + return new GraphQLLayoutService({ + endpoint: config.graphQLEndpoint, + apiKey: config.sitecoreApiKey, + siteName: config.jssAppName, + }); } } diff --git a/samples/nextjs/src/lib/sitemap-fetcher.ts b/samples/nextjs/src/lib/sitemap-fetcher.ts index b3a9bf36e9..ea5d33957d 100644 --- a/samples/nextjs/src/lib/sitemap-fetcher.ts +++ b/samples/nextjs/src/lib/sitemap-fetcher.ts @@ -18,6 +18,7 @@ export class SitecoreSitemapFetcher { constructor() { this._graphqlSitemapService = new GraphQLSitemapService({ endpoint: config.graphQLEndpoint, + apiKey: config.sitecoreApiKey, }); this._disconnectedSitemapService = new DisconnectedSitemapService(