BETA

Example Deployment with Now v2

This deployment example refers to the Now serverless deployment.

This example refers to Now v2. If you are looking for Now v1, check out the Now v1 example.

Prerequisites

Before you get started, you need to have:

We're going to deploy the application under the domain mc-examples-starter.now.sh.

The mc-examples-starter.now.sh domain is already in use. Pick a different one if you choose to deploy using Now.

Configuration

To start, we need to create an env.prod.json file with the following JSON:

{
"applicationName": "mc-examples-starter",
"frontendHost": "mc-examples-starter.now.sh",
"mcApiUrl": "https://mc-api.commercetools.com",
"location": "gcp-eu",
"env": "production",
"cdnUrl": "http://mc-examples-starter.now.sh",
"servedByProxy": true
}

We also need a headers.prod.json to whitelist the domains for the Content Security Policy:

{
"csp": {
"script-src": ["mc-examples-starter.now.sh"],
"connect-src": [
"mc-examples-starter.now.sh",
"mc-api.commercetools.com"
],
"style-src": ["mc-examples-starter.now.sh"]
}
}

To configure Now deployments, we can use a now.json file. Normally, it would look something like this:

{
"version": 2,
"public": true,
"name": "mc-examples-starter.now.sh",
"alias": "mc-examples-starter.now.sh",
"regions": ["bru"],
"builds": [
{
"src": "public/**",
"use": "@now/static"
}
],
"routes": [
{
"src": "/(.*).(js.map|js|css|txt|html|png)",
"dest": "/public/$1.$2",
"headers": {
"cache-control": "s-maxage=31536000,immutable"
}
},
{
"src": "/(.*)",
"dest": "/public/index.html",
"headers": {
"Cache-Control": "no-cache"
}
}
]
}

Some fields may vary based on your setup and requirements, for example public, regions, etc.

However, that won't work just yet as the Custom Application does not have an index.html after building the production bundles. To make it work, we need to compile the application first.

Compile the application

The Merchant Center Custom Applications are available by default with a built-in HTTP server, which takes care of preparing the index.html according to the env.json and headers.json configuration (see Runtime configuration).

To be able to deploy the Custom Application to the version 2 of the ZEIT Now platform, the application needs to be configured and built statically. This is possible using the compile-html command.

mc-scripts compile-html

The command requires to provide the runtime configuration files so that the index.html can be properly compiled.

mc-scripts compile-html \
--headers=$(pwd)/headers.prod.json \
--config=$(pwd)/env.prod.json \
--use-local-assets

The --use-local-assets option is required for the sake of this example. See Serving static assets.

The command above does what we need: it compiles the index.html using the JavaScript bundle references (after running mc-scripts build) and the runtime configuration. At this point the index.html file is ready for production usage.

However, the Custom Application needs to instruct the User-Agent (the browser) to enforce certain security measures, using HTTP headers. The HTTP headers are also compiled together with the index.html, as they rely on the runtime configuration headers.json.

Because of that, the now.json file cannot be defined statically. Instead, it neeeds to be generated programmatically when the Custom Application is built and compiled. To achieve that, we need to implement a transformer function.

Generate now.json using a transformer function

The compile-html command accepts an option transformer which we can use to pass the filesystem path to our transformer function.

We assume that the transformer function is defined at the following location: ./config/transformer-now.js.

mc-scripts compile-html \
--headers=$(pwd)/headers.prod.json \
--config=$(pwd)/env.prod.json \
--use-local-assets \
--transformer $(pwd)/config/transformer-now.js

The purpose of the transformer function is to generate the final now.json given the compiled values passed to the function.

// Function signature using TypeScript
type TransformerFunctionOptions = {
// The content of the `env.json` file.
env: Json;
// The compiled HTTP headers, including CSP (see `loadHeaders` from `@commercetools-frontend/mc-html-template`).
headers: Json;
// The final HTML content of the `index.html`.
indexHtmlContent: string;
}
type TransformerFunction = (options: TransformerFunctionOptions) => void;

The main export of the file should be the transformer function.

transformer-now.jsJavaScript
module.exports = function transformer(options) {
// ...
}

With that in mind, we can implement the transformer function and write the now.json config into the filesystem.

./config/transformer-now.jsJavaScript
const fs = require('fs');
const path = require('path');
const rootPath = path.join(__dirname, '..');
module.exports = function transformer({ headers }) {
const config = {
version: 2,
public: true,
name: 'mc-examples-starter.now.sh',
alias: 'mc-examples-starter.now.sh',
regions: ['bru'],
builds: [
{ src: 'public/**', use: '@now/static' },
],
routes: [
{
src: '/(.*).(js.map|js|css|txt|html|png)',
dest: '/public/$1.$2',
headers: { 'Cache-Control': 's-maxage=31536000,immutable' },
},
{
src: '/(.*)',
dest: '/public/index.html',
headers: { 'Cache-Control': 'no-cache', ...headers },
},
],
};
fs.writeFileSync(
path.join(rootPath, 'now.json'),
JSON.stringify(config, null, 2),
{ encoding: 'utf8' }
);
};

Adding fallback routes

This step is optional and does not prevent the application to be used within the Merchant Center. However, it's recommended to do so to avoid unexpected behaviors in case the URL, where the Custom Application is hosted, is accessed directly.

Accessing the Custom Application directly at https://mc-examples-starter.now.sh won't work, as the application requires the user to log in and thus tries to redirect to the /login route at the same domain.

To prevent that, we can add a dummy fallback route for the login|logout routes. This is only meant to inform the user that the Custom Application cannot be used standalone.

./config/fallback-route.jsJavaScript
module.exports = function fallbackRoute(request, response) {
response.end(
'This is not a real route. If you are seeing this, you most likely are accessing the custom application\n' +
'directly from the hosted domain. Instead, you need to access the custom application from within the Merchant Center\n' +
'domain, as custom applications are served behind a proxy router.\n' +
'To do so, you need to first register the custom application in Merchant Center > Settings > Custom Applications.'
);
};

This route will be used as a serverless function:

./config/transformer-now.jsJavaScript
const fs = require('fs');
const path = require('path');
const rootPath = path.join(__dirname, '..');
module.exports = function transformer({ headers }) {
const config = {
version: 2,
public: true,
name: 'mc-examples-starter.now.sh',
alias: 'mc-examples-starter.now.sh',
regions: ['bru'],
builds: [
{ src: 'public/**', use: '@now/static' },
{ src: 'config/fallback-route.js', use: '@now/node' },
],
routes: [
{
src: '/(.*).(js.map|js|css|txt|html|png)',
dest: '/public/$1.$2',
headers: { 'Cache-Control': 's-maxage=31536000,immutable' },
},
{ src: '/(login|logout)', dest: '/config/fallback-route.js' },
{
src: '/(.*)',
dest: '/public/index.html',
headers: { 'Cache-Control': 'no-cache', ...headers },
},
],
};
fs.writeFileSync(
path.join(rootPath, 'now.json'),
JSON.stringify(config, null, 2),
{ encoding: 'utf8' }
);
};

Deployment

Finally, we can trigger the deployment using the Now CLI:

yarn build
mc-scripts compile-html \
--headers=$(pwd)/headers.prod.json \
--config=$(pwd)/env.prod.json \
--use-local-assets \
--transformer $(pwd)/config/transformer-now.js
now

Now you're ready to Register your Custom Application and start using it!

Developer Center
HTTP APIGraphQL APIBETAPlatform Release NotesCustom ApplicationsBETASDKs & Client LibrariesImport & Export ToolsSUNRISE Starter FrontendsTutorialsFAQ
Merchant Center
DocumentationRelease Notes
Copyright © 2020 commercetools