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

[Nextjs] Allow empty string for getPublicUrl #1656

Merged
merged 15 commits into from
Nov 23, 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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ Our versioning strategy is as follows:
* `[templates/nextjs]` Fix custom headers. Now in cors-header plugin extends custom headers from next.config.js file. ([#1637](https://github.com/Sitecore/jss/pull/1637))
* `[sitecore-jss-react]` Fix PlaceholderCommon with using two and more dynamic placeholders. ([#1641](https://github.com/Sitecore/jss/pull/1641))
* `[templates/nextjs-multisite]` Fix site info fetch errors (now skipped) on XM Cloud rendering/editing host builds. ([#1649](https://github.com/Sitecore/jss/pull/1649)), ([#1653](https://github.com/Sitecore/jss/pull/1653))

* `[templates/nextjs]` `[sitecore-jss-nextjs]` Fix making a fetch to a nextjs api route in an editing environment, by adding additional variable publicUrl in runtime config ([#1656](https://github.com/Sitecore/jss/pull/1656))
*
### 🛠 Breaking Changes

* `[create-sitecore-jss]` The `nextjs-personalize` initializer add-on template has been removed and is replaced by the `nextjs-xmcloud` initializer template. You can use the interactive prompts or the `--xmcloud` argument to include this template. ([#1653](https://github.com/Sitecore/jss/pull/1653))
Expand All @@ -49,6 +50,7 @@ Our versioning strategy is as follows:
* _PersonalizeMiddleware_ has transitioned to utilizing the _CloudSDK_ package, replacing the previous dependency on _Engage_.
* Introduced _Context_ class, that is used to initialize the application Context and shared Software Development Kits (SDKs). Accessible within the _@sitecore-jss/sitecore-jss-nextjs/context_ submodule.
* Point of Sale resolution is fully removed, now it's handled by Sitecore Edge Proxy
* `[templates/nextjs]` `[sitecore-jss-nextjs]` The behavior of getPublicUrl() function has been changed - empty string is now considered valid value for PUBLIC_URL environment variable and, if defined, PUBLIC_URL will take precedence over the Vercel/Netlify env variables; the values of these variables should be adjusted as needed; PUBLIC_URL is commented out by default in .env; ([#1656](https://github.com/Sitecore/jss/pull/1656));

## 21.5.0

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Link from 'next/link';
import { useI18n } from 'next-localization';
import { getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs/utils';
import config from 'temp/config';

// Prefix public assets with a public URL to enable compatibility with Sitecore Experience Editor.
// If you're not supporting the Experience Editor, you can remove this.
const publicUrl = getPublicUrl();
// Prefix public assets with a public URL to enable compatibility with Sitecore editors.
// If you're not supporting Sitecore editors, you can remove this.
const publicUrl = config.publicUrl;

const Navigation = (): JSX.Element => {
const { t } = useI18n();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Link from 'next/link';
import { useI18n } from 'next-localization';
import { getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs/utils';
import config from 'temp/config';

// Prefix public assets with a public URL to enable compatibility with Sitecore Experience Editor.
// If you're not supporting the Experience Editor, you can remove this.
const publicUrl = getPublicUrl();
// Prefix public assets with a public URL to enable compatibility with Sitecore editors.
// If you're not supporting Sitecore editors, you can remove this.
const publicUrl = config.publicUrl;

const Navigation = (): JSX.Element => {
const { t } = useI18n();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,13 @@
*/
import React from 'react';
import Head from 'next/head';
import {
Placeholder,
LayoutServiceData,
Field,
HTMLLink,
} from '@sitecore-jss/sitecore-jss-nextjs';
import { getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs/utils';
import { Placeholder, LayoutServiceData, Field, HTMLLink } from '@sitecore-jss/sitecore-jss-nextjs';
import config from 'temp/config';
import Scripts from 'src/Scripts';

// Prefix public assets with a public URL to enable compatibility with Sitecore Experience Editor.
// If you're not supporting the Experience Editor, you can remove this.
const publicUrl = getPublicUrl();
const publicUrl = config.publicUrl;

interface LayoutProps {
layoutData: LayoutServiceData;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { AxiosDataFetcher, GraphQLSitemapXmlService, AxiosResponse } from '@sitecore-jss/sitecore-jss-nextjs';
import { getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs/utils';
import { siteResolver } from 'lib/site-resolver';
import config from 'temp/config';

Expand Down Expand Up @@ -51,14 +50,16 @@ const sitemapApi = async (
if (!sitemaps.length) {
return res.redirect('/404');
}


const reqtHost = req.headers.host;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this handle localhost?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, tested

const reqProtocol = req.headers['x-forwarded-proto'] || 'https';
const SitemapLinks = sitemaps
.map((item) => {
const parseUrl = item.split('/');
const lastSegment = parseUrl[parseUrl.length - 1];

return `<sitemap>
<loc>${getPublicUrl()}/${lastSegment}</loc>
<loc>${reqProtocol}://${reqtHost}/${lastSegment}</loc>
</sitemap>`;
})
.join('');
Expand Down
4 changes: 3 additions & 1 deletion packages/create-sitecore-jss/src/templates/nextjs/.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
# in your Sitecore configuration (see \sitecore\config\<%- appName %>.config).
# Be sure to update these values accordingly as your public endpoint changes.
# See https://jss.sitecore.com/docs/fundamentals/services/view-engine
PUBLIC_URL=http://localhost:3000
# If undefined, http://localhost:3000 is used by default.
# In production non-editing environments it is desirable to use relative URLs, so this may be set to empty string.
#PUBLIC_URL=

# To secure the Sitecore editor endpoint exposed by your Next.js app
# (`/api/editing/render` by default), a secret token is used. This (client-side)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
const jssConfig = require('./src/temp/config');
const { getPublicUrl } = require('@sitecore-jss/sitecore-jss-nextjs/utils');
const plugins = require('./src/temp/next-config-plugins') || {};

const publicUrl = getPublicUrl();
const publicUrl = jssConfig.publicUrl;

/**
* @type {import('next').NextConfig}
Expand All @@ -27,7 +26,7 @@ const nextConfig = {
// prefixed path e.g. `/styleguide`.
defaultLocale: jssConfig.defaultLanguage,
},

// Enable React Strict Mode
reactStrictMode: true,

Expand All @@ -53,12 +52,12 @@ const nextConfig = {
{
source: '/sitecore/service/:path*',
destination: `${jssConfig.sitecoreApiHost}/sitecore/service/:path*`,
},
},
];
},
};

module.exports = () => {
// Run the base config through any configured plugins
return Object.values(plugins).reduce((acc, plugin) => plugin(acc), nextConfig);
}
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { JssConfig } from 'lib/config';
import { ConfigPlugin } from '..';
import { getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs/utils';

/**
* This config will set fallback values for properties that were left empty
Expand All @@ -14,6 +15,7 @@ class FallbackPlugin implements ConfigPlugin {
defaultLanguage: config.defaultLanguage || 'en',
sitecoreApiKey: config.sitecoreApiKey || 'no-api-key-set',
layoutServiceConfigurationName: config.layoutServiceConfigurationName || 'default',
publicUrl: config.publicUrl || getPublicUrl(),
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const defaultConfig: JssConfig = {
defaultLanguage: process.env[`${constantCase('defaultLanguage')}`],
graphQLEndpoint: process.env[`${constantCase('graphQLEndpoint')}`],
layoutServiceConfigurationName: process.env[`${constantCase('layoutServiceConfigurationName')}`],
publicUrl: process.env[`${constantCase('publicUrl')}`],
};

generateConfig(defaultConfig);
Expand Down Expand Up @@ -56,6 +57,7 @@ const config = {};\n`;
Object.keys(config).forEach(prop => {
configText += `config.${prop} = process.env.${constantCase(prop)} || '${config[prop]}',\n`;
});

configText += `module.exports = config;`;

const configPath = path.resolve('src/temp/config.js');
Expand Down
11 changes: 3 additions & 8 deletions packages/create-sitecore-jss/src/templates/nextjs/src/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import React from 'react';
import Head from 'next/head';
import {
Placeholder,
LayoutServiceData,
Field,
HTMLLink,
} from '@sitecore-jss/sitecore-jss-nextjs';
import { getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs/utils';
import { Placeholder, LayoutServiceData, Field, HTMLLink } from '@sitecore-jss/sitecore-jss-nextjs';
import config from 'temp/config';
import Navigation from 'src/Navigation';
import Scripts from 'src/Scripts';

// Prefix public assets with a public URL to enable compatibility with Sitecore editors.
// If you're not supporting Sitecore editors, you can remove this.
const publicUrl = getPublicUrl();
const publicUrl = config.publicUrl;

interface LayoutProps {
layoutData: LayoutServiceData;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs/utils';
import config from 'temp/config';

// Prefix public assets with a public URL to enable compatibility with Sitecore editors.
// If you're not supporting Sitecore editors, you can remove this.
const publicUrl = getPublicUrl();
const publicUrl = config.publicUrl;

const Navigation = (): JSX.Element => (
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export interface JssConfig extends Record<string, string | undefined> {
defaultLanguage?: string;
graphQLEndpoint?: string;
layoutServiceConfigurationName?: string;
publicUrl?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,27 @@ const corsHeaderPlugin = (nextConfig = {}) => {
}
return Object.assign({}, nextConfig, {
async headers() {
const extendHeaders = typeof nextConfig.headers === "function" ? await nextConfig.headers() : [];
const extendHeaders =
typeof nextConfig.headers === 'function' ? await nextConfig.headers() : [];
return [
...await extendHeaders,
...(await extendHeaders),
{
source: '/_next/:path*',
headers: [{ key: 'Access-Control-Allow-Origin', value: config.sitecoreApiHost }],
headers: [
{
key: 'Access-Control-Allow-Origin',
value: config.sitecoreApiHost.replace(/\/$/, ''),
},
],
},
{
source: '/api/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: config.sitecoreApiHost.replace(/\/$/, ''),
},
],
},
];
},
Expand Down
29 changes: 23 additions & 6 deletions packages/sitecore-jss-nextjs/src/utils/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getPublicUrl, getJssEditingSecret } from './utils';

describe('utils', () => {
describe('getPublicUrl', () => {
after(() => {
afterEach(() => {
delete process.env.PUBLIC_URL;
delete process.env.VERCEL_URL;
});
Expand All @@ -28,15 +28,32 @@ describe('utils', () => {
expect(result).to.equal('http://test.com/foo');
});

it('should throw for invalid URL', () => {
process.env.PUBLIC_URL = 'nope';
expect(() => getPublicUrl()).to.throw();
it('should use VERCEL_URL if PUBLIC_URL is not defined', () => {
process.env.VERCEL_URL = 'jss.uniqueid.vercel.com';
const result = getPublicUrl();
expect(result).to.equal('https://jss.uniqueid.vercel.com');
});

it('should use VERCEL_URL', () => {
it('should use PUBLIC_URL if PUBLIC_URL and VERCEL_URL are defined', () => {
process.env.VERCEL_URL = 'jss.uniqueid.vercel.com';
process.env.PUBLIC_URL = 'http://test.com';
const result = getPublicUrl();
expect(result).to.equal('https://jss.uniqueid.vercel.com');
expect(result).to.equal('http://test.com');
});

it('should use PUBLIC_URL if it is an empty string and VERCEL_URL is defined', () => {
process.env.VERCEL_URL = 'jss.uniqueid.vercel.com';
const publicUrl = '';
process.env.PUBLIC_URL = publicUrl;
const result = getPublicUrl();
expect(result).to.equal(publicUrl);
});

it('should use PUBLIC_URL if it is defined and is empty string', () => {
const publicUrl = '';
process.env.PUBLIC_URL = publicUrl;
const result = getPublicUrl();
expect(result).to.equal(publicUrl);
});
});
describe('getJssEditingSecret', () => {
Expand Down
23 changes: 7 additions & 16 deletions packages/sitecore-jss-nextjs/src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import chalk from 'chalk';
import { isEditorActive, resetEditorChromes } from '@sitecore-jss/sitecore-jss/utils';

/**
Expand All @@ -8,28 +7,20 @@ import { isEditorActive, resetEditorChromes } from '@sitecore-jss/sitecore-jss/u
* VERCEL_URL is provided by Vercel in case if we are in Preview deployment (deployment based on the custom branch),
* preview deployment has unique url, we don't know exact url.
* Similarly, DEPLOY_URL is provided by Netlify and would give us the deploy URL
* In production non-editing environments it is desirable to use relative urls, so in that case set PUBLIC_URL = ''
*/
export const getPublicUrl = (): string => {
if (process.env.NETLIFY && process.env.DEPLOY_URL) return process.env.DEPLOY_URL;
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;

let url = process.env.PUBLIC_URL;

if (url === undefined) {
console.warn(
`${chalk.yellow.bold(
'Warning:'
)} An PUBLIC_URL environment variable is not defined. Falling back to http://localhost:3000.`
);
if (process.env.NETLIFY && process.env.DEPLOY_URL) return process.env.DEPLOY_URL;
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;

url = 'http://localhost:3000';
} else {
try {
new URL(url);
} catch (error) {
throw new Error(`The PUBLIC_URL environment variable '${url}' is not a valid URL.`);
}
}

// Ensure no trailing slash
return url.toString().replace(/\/$/, '');
return url.replace(/\/$/, '');
};

/**
Expand Down