All Collections
Advanced patterns
Creating a landing page presenting all available stores to visitors
Creating a landing page presenting all available stores to visitors

You have per-region websites (/eu-en, /eu-fr, /us-en, …) and want to display a root page to point visitors to the most accurate one.

Pierre avatar
Written by Pierre
Updated over a week ago

Context

Your Front-Commerce instance is a multi-store instance.
Each store has its own URL and Front-Commerce uses the correct context based on a store URL.

This article explains how to create and render a page that is not bound to any store, so that you can display a landing page for your visitors.

For instance, https://www.example.com will display a page linking to each region-specific URL:

Register your route

First, you'll have to create and register a server module.

// .front-commerce.js
module.exports = {
name: "Front Commerce",
url: "http://www.front-commerce.test",
- modules: ["./src"],
+ modules: ["./src", "./my-module"],
serverModules: [

This module will add custom endpoints to the server, as detailed in our documentation.

For this example, we want to register a root endpoint that is shared for the whole server. It will be executed before any other express route middlewares.

Here is how to do it:

// my-module/server/module.config.js
import { Router } from "express";

// This could be extracted it in a different file to ease maintenance
const router = () => {
const router = new Router();

router.get("/", (req, res, next) => {
res.send("Hello from the landing page");
});

return router;
};

export default {
// see https://developers.front-commerce.com/docs/advanced/server/add-http-endpoint.html#Subdirectory-based-urls-and-global-middleware
rootEndpoint: {
__dangerouslyOverrideBasePathChecks: true,
path: "/",
router: router,
},
};

🎉 You must now view a Hello from the landing page message when browsing the https://www.example.com/ page!

Render a simple React component

You'll usually want to display more than a single text sentence.

You could replace the string in the previous example by anything: static HTML from a file, from a remote URL or service. It is standard Express code.

If you want to render a React component, you can use ReactDOMServer.renderToStaticMarkup(element) (documentation):

// my-module/server/module.config.js
import { Router } from "express";
+ import TopLevelHome from "theme/pages/TopLevelHome";
+
+ import React from "react";
+ import ReactDOMServer from "react-dom/server";

// This could be extracted it in a different file to ease maintenance
const router = () => {
const router = new Router();

router.get("/", (req, res, next) => {
- res.send("Hello from the landing page");
+ const content = ReactDOMServer.renderToStaticMarkup(<TopLevelHome />);
+ res.send(result);
});

return router;
};

This will work for simple React components that don't use Front-Commerce specific features.

Example:

// my-module/web/theme/pages/TopLevelHome.js
import React from "react";

const TopLevelHome = () => {
return (
<div>
<h1>Welcome</h1>
<ul>
<li>
<a href="/eu-fr">Go to 🇫🇷</a>
</li>
<li>
<a href="/eu-en">Go to 🏴󠁧󠁢󠁥󠁮󠁧󠁿</a>
</li>
<li>
<a href="/us-en">Go to 🇺🇸</a>
</li>
</ul>
</div>
);
};

export default TopLevelHome;

🎉 You should now view your React component when refreshing the page.

Render Front-Commerce UI components

If you want to reuse existing components from your application, we recommend to keep them to very simple UI components (headings, cards, logo…).

It reduces the risk of introducing store specific content in the future, which could break this landing page.

ℹ️ This is what is done for offline and maintenance pages for instance.

These components might however require to be rendered in a Front-Commerce context.

Here is how to adapt the previous code to:

  • reuse Front-Commerce's Server Side Rendering internal functions

  • use a minimal HTML template that works with Front-Commerce's SSR

⚠️ The example below is only rendered on the server. It is not hydrated on the client. You should ensure your code works in this context (e.g: use <Image priority src="…" /> etc…)

import { Router } from "express";
import TopLevelHome from "theme/pages/TopLevelHome";

import path from "path";
import renderStaticPage from "server/express/ssr/renderStaticPage";
import { ChunkExtractor } from "@loadable/server";
import { getShopConfig } from "server/core/config/getShopConfig";

// A simplified template for SSR page without hydration (adapt it!)
const htmlTemplate = `
<!DOCTYPE html>
<html
%%__HTML_ATTRIBUTE__%%
prefix="og: http://ogp.me/ns# product: http://ogp.me/ns/product#"
>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="apple-touch-icon"
href="%%__ASSETS_BASE_URL__%%/images/logos/icon-192x192.png"
/>
<link rel="shortcut icon" href="%%__ASSETS_BASE_URL__%%/favicon.ico" />
<link rel="manifest" href="%%__ASSETS_BASE_URL__%%/manifest.json" />
%%__HEAD__%% %%__HELMET__%%
</head>

<body>
<div id="app">%%__CONTENT__%%</div>
<script>
window.__BASE_URL__ = "%%__BASE_URL__%%";
</script>
<script>
window.__ASSETS_BASE_URL__ = "%%__ASSETS_BASE_URL__%%";
</script>
</body>
</html>
`;

const router = () => {
const router = new Router();

router.get("/", (req, res, next) => {
// use a specific shop configuration as a base
const shopConfig = getShopConfig(req.config, "default");

const extractor = new ChunkExtractor({
publicPath:
process.env.NODE_ENV === "production" ? shopConfig.assetsBaseUrl : "/",
statsFile: path.resolve(
process.cwd(),
"./build/client/loadable-stats.json"
),
});

// ensure that all styles are available
extractor.getLinkTags = () => {
return extractor.getLinkTags() + extractor.getStyleTags();
};
renderStaticPage(
Promise.resolve(htmlTemplate),
TopLevelHome,
200,
false,
{
...shopConfig,
// override the default configuration as you wish
locale: req.headers["accept-language"]?.split(",")[0], // ⚠️ naive example!
},
extractor
)(req, res, next).catch((e) => {
console.error(e);
// redirect the user anyway in case something failed
res.redirect(shopConfig.url);
});
});

return router;
};

export default {
// see https://developers.front-commerce.com/docs/advanced/server/add-http-endpoint.html#Subdirectory-based-urls-and-global-middleware
rootEndpoint: {
__dangerouslyOverrideBasePathChecks: true,
path: "/",
router: router,
},
};

🎉 You should now be able to render more advanced pages that can leverage media queries, device detection, i18n and other Front-Commerce features.

Based on this example, we invite you to adapt and improve this code to match your specific use case.

Don't hesitate to contact us if you face specific issues.

Did this answer your question?