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:
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:
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 value | Description |
|---|---|
{ 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. |
null | Use the default behavior (redirect to the first configured store). |
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:
| Property | Type | Description |
|---|---|---|
request | Request | The incoming HTTP request (Web Fetch API Request object). |
config | ComputedConfig | The current Front-Commerce configuration. |
user | User | The current user session information. |
services | Services["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" };
};