Skip to main content
Version: next

Customize shop fallback redirect

When a user visits a URL that doesn't match any configured shop, Front-Commerce redirects to a default shop. This guide explains how to customize this behavior using the onShopFallback lifecycle hook.

Understanding shop fallback

In a multi-store setup, each store is associated with a specific URL (e.g., example.com/fr, example.com/en). When a user visits a URL that doesn't match any configured store (e.g., example.com/), Front-Commerce needs to decide what to do.

By default, Front-Commerce redirects to the first configured store. The onShopFallback lifecycle hook allows you to customize this behavior — for instance, redirecting users based on their browser language preferences.

Using the onShopFallback hook

The onShopFallback hook is an unstable lifecycle hook that runs when no shop matches the current request URL. It receives the full request context and can return different actions.

Create an extension with the hook

Create a new extension file:

extensions/shopFallbackExtension.ts
import { defineExtension } from "@front-commerce/core";

export default function shopFallbackExtension() {
return defineExtension({
name: "shop-fallback-redirect",
meta: import.meta,
unstable_lifecycleHooks: {
onShopFallback: async ({ request, config, user, services }) => {
const acceptLanguage = request.headers.get("Accept-Language") || "";

// Redirect French speakers to the French store
if (acceptLanguage.toLowerCase().startsWith("fr")) {
return { type: "shopId", shopId: "fr" };
}

// Redirect German speakers to a custom URL
if (acceptLanguage.toLowerCase().startsWith("de")) {
return { type: "url", url: "https://example.de" };
}

// Use default behavior (redirect to first configured store)
return null;
},
},
});
}

Register the extension

Add your extension to front-commerce.config.ts:

front-commerce.config.ts
import { defineConfig } from "@front-commerce/core/config";
import themeChocolatine from "@front-commerce/theme-chocolatine";
import shopFallbackExtension from "./extensions/shopFallbackExtension";
import storesConfig from "./app/config/stores";
import cacheConfig from "./app/config/caching";

export default defineConfig({
extensions: [
themeChocolatine(),
shopFallbackExtension(),
],
stores: storesConfig,
cache: cacheConfig,
});

Hook return values

The onShopFallback hook can return one of the following:

Return valueDescription
{ type: "shopId", shopId: string }Redirect to the specified shop. The original path and query parameters are preserved.
{ type: "url", url: string }Redirect to a custom URL. Use this for external redirects or when you need full control over the destination.
{ type: "skip" }Skip the redirect and continue processing the request. The fallback shop config is used, but the URL remains unchanged — this typically results in a 404 unless a matching route exists.
nullUse the default behavior (redirect to the first configured store).
tip

The skip option is useful when you have a custom route defined at the root URL (e.g., a landing page at / or health check endpoints). Without a matching route, the request will result in a 404.

Hook context

The hook receives a context object with the following properties:

PropertyTypeDescription
requestRequestThe incoming HTTP request (Web Fetch API Request object).
configComputedConfigThe current Front-Commerce configuration.
userUserThe current user session information.
servicesServices["server"]Server-side services (e.g., for accessing external APIs).

Multiple hooks

If multiple extensions register an onShopFallback hook, they are called in order until one returns a non-null value. This allows you to compose fallback logic across extensions.

// Extension A
onShopFallback: ({ request }) => {
// Handle specific case
if (someCondition) {
return { type: "shopId", shopId: "special" };
}
return null; // Pass to next hook
};

// Extension B (called if A returns null)
onShopFallback: ({ request }) => {
// Default language-based logic
return { type: "shopId", shopId: "en" };
};