vtex-io-rootpath
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVTEX IO rootPath for multi-binding stores
面向多绑定商店的VTEX IO rootPath
When this skill applies
本技能的适用场景
Use this skill when building VTEX IO apps that must work in stores with multi-binding configurations—typically cross-border stores where multiple bindings share a single domain with path prefixes (e.g. , , ).
store.com/us/store.com/br/store.com/mx/- Your app generates URLs (links, redirects, API endpoints, canonical URLs) that must include the binding's path prefix
- Your app loads assets (images, scripts, stylesheets) that break when the store uses a sub-path binding
- Your backend routes need to construct URLs for sitemaps, canonical links, or cross-binding references
- You're debugging 404s or wrong links that only appear in multi-binding stores but work fine in single-binding
Do not use this skill for:
- Single-binding stores with a dedicated domain per locale (no path prefix needed)
- General IO backend patterns (use )
vtex-io-service-apps - CDN/edge caching configuration (use )
vtex-io-service-paths-and-cdn
当你构建的VTEX IO应用需要在多绑定配置的商店(通常是跨境商店,多个绑定共享单个域名并使用路径前缀,例如、、)中运行时,请使用本技能。
store.com/us/store.com/br/store.com/mx/- 你的应用生成的URL(链接、重定向、API端点、规范URL)需要包含绑定的路径前缀
- 当商店使用子路径绑定时,你的应用加载的资源(图片、脚本、样式表)出现异常
- 你的后端路由需要为站点地图、规范链接或跨绑定引用构建URL
- 你正在调试仅在多绑定商店中出现、但在单绑定商店中正常的404错误或错误链接
请勿在以下场景使用本技能:
- 每个区域设置有专用域名的单绑定商店(无需路径前缀)
- 通用IO后端模式(请使用)
vtex-io-service-apps - CDN/边缘缓存配置(请使用)
vtex-io-service-paths-and-cdn
Decision rules
决策规则
- Single-domain multi-binding (e.g. ,
store.com/us/) →store.com/br/is required. Every generated URL must be prefixed with the binding's root path.rootPath - Multi-domain single-binding (e.g. ,
store.us) →store.com.bris typically empty orrootPath. URLs work without prefixing, but code should still handle/gracefully (use it if non-empty, skip if empty).rootPath - Backend (Node) → The platform sends the binding's path prefix in the request header. Your app needs an early middleware (typically called
x-vtex-root-path) that reads this header, sanitizes it, and stores it onprepare. Once set up, all downstream handlers readctx.state.rootPathdirectly. The middleware must also setctx.state.rootPathso CDN caching works correctly per binding.Vary: x-vtex-root-path - Frontend (React) → Use from
useRuntime().rootPathto get the current binding's path prefix in components.vtex.render-runtime - Always prefix, never hardcode — Never hardcode a path prefix like . Always use the runtime-provided
/us/so the same code works across all bindings.rootPath
- 单域名多绑定(例如、
store.com/us/)→ 必须使用store.com/br/。所有生成的URL都必须添加绑定的根路径前缀。rootPath - 多域名单绑定(例如、
store.us)→store.com.br通常为空或rootPath。无需前缀即可正常生成URL,但代码仍需优雅处理/(非空时使用,为空时跳过)。rootPath - 后端(Node) → 平台会在请求头中发送绑定的路径前缀。你的应用需要一个早期中间件(通常名为
x-vtex-root-path)来读取该请求头,对其进行清理,并将其存储在prepare中。设置完成后,所有下游处理器直接读取ctx.state.rootPath。该中间件还必须设置ctx.state.rootPath,以确保CDN针对每个绑定正确缓存。Vary: x-vtex-root-path - 前端(React) → 使用中的
vtex.render-runtime在组件中获取当前绑定的路径前缀。useRuntime().rootPath - 始终使用前缀,绝不硬编码 — 永远不要硬编码这类路径前缀。始终使用运行时提供的
/us/,以便同一代码可在所有绑定中正常工作。rootPath
Hard constraints
硬性约束
Constraint: Always use rootPath when constructing URLs in multi-binding stores
约束:在多绑定商店中构建URL时必须使用rootPath
Every URL your app generates—links, redirects, API endpoints, canonical URLs, sitemap entries—must include the prefix when the store uses path-based bindings.
rootPathWhy this matters — Without , a link to in the binding points to the wrong binding (or 404s). Sitemaps with unprefixed URLs break SEO by pointing search engines to the wrong locale. Redirects without the prefix send users to the default binding instead of their current one.
rootPath/my-account/orders/br/Detection — URLs constructed as string literals (e.g. ) without prepending . Or calls that omit the prefix.
/${slug}rootPathnavigate()rootPathCorrect — Prepend rootPath (parsed by prepare middleware) to all generated paths.
typescript
// Backend: use ctx.state.rootPath (parsed by prepare middleware)
const { rootPath, forwardedHost } = ctx.state;
const canonicalUrl = `https://${forwardedHost}${rootPath}/product/${slug}`;
const sitemapEntry = `${rootPath}/${categoryPath}`;tsx
// Frontend: use runtime hook
import { useRuntime } from "vtex.render-runtime";
const MyLink = ({ slug }: { slug: string }) => {
const { rootPath } = useRuntime();
return <a href={`${rootPath}/product/${slug}`}>View product</a>;
};Wrong — Hardcoded paths without rootPath.
typescript
// Backend: missing rootPath — breaks in multi-binding
const canonicalUrl = `https://${host}/product/${slug}`
// Frontend: hardcoded path
const MyLink = ({ slug }: { slug: string }) => {
return <a href={`/product/${slug}`}>View product</a>
}当商店使用基于路径的绑定时,你的应用生成的每个URL(链接、重定向、API端点、规范URL、站点地图条目)都必须包含前缀。
rootPath重要性 — 如果没有,绑定中指向的链接会指向错误的绑定(或返回404)。未添加前缀的站点地图会导致搜索引擎索引错误区域设置的URL,破坏SEO。未添加前缀的重定向会将用户发送到默认绑定而非当前绑定。
rootPath/br//my-account/orders检测方式 — 以字符串字面量构建的URL(例如)未添加前缀。或者调用省略了前缀。
/${slug}rootPathnavigate()rootPath正确示例 — 将所有生成的路径添加rootPath前缀(由prepare中间件解析)。
typescript
// Backend: use ctx.state.rootPath (parsed by prepare middleware)
const { rootPath, forwardedHost } = ctx.state;
const canonicalUrl = `https://${forwardedHost}${rootPath}/product/${slug}`;
const sitemapEntry = `${rootPath}/${categoryPath}`;tsx
// Frontend: use runtime hook
import { useRuntime } from "vtex.render-runtime";
const MyLink = ({ slug }: { slug: string }) => {
const { rootPath } = useRuntime();
return <a href={`${rootPath}/product/${slug}`}>View product</a>;
};错误示例 — 未添加rootPath的硬编码路径。
typescript
// Backend: missing rootPath — breaks in multi-binding
const canonicalUrl = `https://${host}/product/${slug}`
// Frontend: hardcoded path
const MyLink = ({ slug }: { slug: string }) => {
return <a href={`/product/${slug}`}>View product</a>
}Constraint: Sanitize rootPath to avoid double slashes
约束:清理rootPath以避免双斜杠
When is (single-binding or default binding), using it directly produces double slashes in URLs (e.g. ). Normalize: if , treat it as .
rootPath"/"//product/shoesrootPath === "/"""Why this matters — Double slashes in URLs cause redirect loops, broken canonical URLs, and SEO penalties. Some CDN layers treat differently from .
//path/pathDetection — URL construction that concatenates without checking for .
rootPath + "/" + pathrootPath === "/"Correct
typescript
// In the prepare middleware, sanitize before storing on state:
let rootPath = ctx.get("x-vtex-root-path");
if (rootPath && !rootPath.startsWith("/")) {
rootPath = `/${rootPath}`;
}
if (rootPath === "/") {
rootPath = "";
}
ctx.state.rootPath = rootPath;
// Downstream: ctx.state.rootPath is already sanitized
const { rootPath } = ctx.state;
const url = `${rootPath}/${path}`;Wrong
typescript
const rootPath = ctx.get("x-vtex-root-path") || "/";
const url = `${rootPath}/${path}`; // Produces "//path" for default binding当为(单绑定或默认绑定)时,直接使用会导致URL中出现双斜杠(例如)。需进行规范化处理:如果,则将其视为。
rootPath"/"//product/shoesrootPath === "/"""重要性 — URL中的双斜杠会导致重定向循环、规范URL失效以及SEO惩罚。部分CDN层对和的处理方式不同。
//path/path检测方式 — URL构建时直接拼接,未检查。
rootPath + "/" + pathrootPath === "/"正确示例
typescript
// In the prepare middleware, sanitize before storing on state:
let rootPath = ctx.get("x-vtex-root-path");
if (rootPath && !rootPath.startsWith("/")) {
rootPath = `/${rootPath}`;
}
if (rootPath === "/") {
rootPath = "";
}
ctx.state.rootPath = rootPath;
// Downstream: ctx.state.rootPath is already sanitized
const { rootPath } = ctx.state;
const url = `${rootPath}/${path}`;错误示例
typescript
const rootPath = ctx.get("x-vtex-root-path") || "/";
const url = `${rootPath}/${path}`; // Produces "//path" for default bindingPreferred pattern
推荐模式
State interface with rootPath
包含rootPath的状态接口
Declare and related binding state on your interface so all handlers have typed access:
rootPathStatetypescript
// node/typings.d.ts
declare global {
interface State extends RecorderState {
binding: Binding;
rootPath: string;
forwardedHost: string;
forwardedPath: string;
isCrossBorder: boolean;
matchingBindings: Binding[];
}
type Context = ServiceContext<Clients, State>;
}在接口中声明及相关绑定状态,以便所有处理器都能通过类型化访问:
StaterootPathtypescript
// node/typings.d.ts
declare global {
interface State extends RecorderState {
binding: Binding;
rootPath: string;
forwardedHost: string;
forwardedPath: string;
isCrossBorder: boolean;
matchingBindings: Binding[];
}
type Context = ServiceContext<Clients, State>;
}Prepare middleware (parses rootPath from header)
Prepare中间件(从请求头解析rootPath)
Wire a middleware early in every route's middleware chain. It reads the header, sanitizes it, resolves the current binding, and sets headers so the CDN caches responses per binding:
preparex-vtex-root-pathVarytypescript
// node/middlewares/prepare.ts
const FORWARDED_HOST_HEADER = "x-forwarded-host";
const VTEX_ROOT_PATH_HEADER = "x-vtex-root-path";
export async function prepare(ctx: Context, next: () => Promise<void>) {
const forwardedHost = ctx.get(FORWARDED_HOST_HEADER);
let rootPath = ctx.get(VTEX_ROOT_PATH_HEADER);
// Defend against malformed root path — must start with /
if (rootPath && !rootPath.startsWith("/")) {
rootPath = `/${rootPath}`;
}
// Normalize "/" to "" to avoid double slashes in URL construction
if (rootPath === "/") {
rootPath = "";
}
const [forwardedPath] = ctx.get("x-forwarded-path").split("?");
ctx.state = {
...ctx.state,
forwardedHost,
forwardedPath,
rootPath,
// ... resolve binding, matchingBindings, etc.
};
await next();
// Vary on these headers so CDN caches separate responses per binding
ctx.vary(FORWARDED_HOST_HEADER);
ctx.vary(VTEX_ROOT_PATH_HEADER);
}Downstream handlers then use directly — no header parsing needed:
ctx.state.rootPathtypescript
// node/middlewares/generateSitemap.ts
export async function generateSitemap(ctx: Context, next: () => Promise<void>) {
const { rootPath, binding } = ctx.state;
const canonicalUrl = `https://${ctx.state.forwardedHost}${rootPath}/${slug}`;
// ...
}在每个路由的中间件链早期添加中间件。它会读取请求头,对其进行清理,解析当前绑定,并设置请求头,以便CDN针对每个绑定缓存响应:
preparex-vtex-root-pathVarytypescript
// node/middlewares/prepare.ts
const FORWARDED_HOST_HEADER = "x-forwarded-host";
const VTEX_ROOT_PATH_HEADER = "x-vtex-root-path";
export async function prepare(ctx: Context, next: () => Promise<void>) {
const forwardedHost = ctx.get(FORWARDED_HOST_HEADER);
let rootPath = ctx.get(VTEX_ROOT_PATH_HEADER);
// 防御格式错误的根路径 — 必须以/开头
if (rootPath && !rootPath.startsWith("/")) {
rootPath = `/${rootPath}`;
}
// 将"/"规范化为空字符串,避免URL构建时出现双斜杠
if (rootPath === "/") {
rootPath = "";
}
const [forwardedPath] = ctx.get("x-forwarded-path").split("?");
ctx.state = {
...ctx.state,
forwardedHost,
forwardedPath,
rootPath,
// ... 解析binding、matchingBindings等
};
await next();
// 设置这些请求头的Vary字段,以便CDN针对每个绑定缓存不同的响应
ctx.vary(FORWARDED_HOST_HEADER);
ctx.vary(VTEX_ROOT_PATH_HEADER);
}下游处理器随后可直接使用 — 无需再解析请求头:
ctx.state.rootPathtypescript
// node/middlewares/generateSitemap.ts
export async function generateSitemap(ctx: Context, next: () => Promise<void>) {
const { rootPath, binding } = ctx.state;
const canonicalUrl = `https://${ctx.state.forwardedHost}${rootPath}/${slug}`;
// ...
}Frontend utility
前端工具函数
tsx
import { useRuntime } from "vtex.render-runtime";
function usePrefixedPath(path: string): string {
const { rootPath = "" } = useRuntime();
const prefix = rootPath === "/" ? "" : rootPath;
return `${prefix}${path.startsWith("/") ? path : `/${path}`}`;
}tsx
import { useRuntime } from "vtex.render-runtime";
function usePrefixedPath(path: string): string {
const { rootPath = "" } = useRuntime();
const prefix = rootPath === "/" ? "" : rootPath;
return `${prefix}${path.startsWith("/") ? path : `/${path}`}`;
}Binding-aware API calls from frontend
前端的绑定感知API调用
tsx
const { rootPath, binding } = useRuntime();
// binding.id — current binding ID
// binding.canonicalBaseAddress — e.g. "store.com/br"
// rootPath — e.g. "/br"
// When calling backend APIs, the platform handles rootPath automatically
// for IO-internal calls. For external URLs or custom redirects, prefix manually.tsx
const { rootPath, binding } = useRuntime();
// binding.id — 当前绑定ID
// binding.canonicalBaseAddress — 例如 "store.com/br"
// rootPath — 例如 "/br"
// 调用后端API时,平台会自动处理IO内部调用的rootPath。对于外部URL或自定义重定向,请手动添加前缀。Common failure modes
常见故障模式
- Links break in multi-binding — Navigation links constructed without send users to the wrong binding or 404.
rootPath - Sitemap has wrong URLs — Sitemap generator omits , causing search engines to index unprefixed URLs that resolve to the default binding.
rootPath - Double slashes — concatenated with
rootPath === "/"produces/path. Normalize to empty string.//path - Hardcoded locale paths — Using or
/us/instead of dynamic/br/. Breaks when bindings are reconfigured.rootPath - Backend ignores header — Node service constructs URLs without reading , producing wrong canonicals in multi-binding.
x-vtex-root-path - Missing Vary header — Response doesn't set and
Vary: x-vtex-root-path, causing the CDN to serve the same cached response for different bindings.Vary: x-forwarded-host
- 多绑定中链接失效 — 未使用构建的导航链接会将用户发送到错误的绑定或返回404。
rootPath - 站点地图包含错误URL — 站点地图生成器未添加,导致搜索引擎索引未添加前缀的URL,这些URL会解析到默认绑定。
rootPath - 双斜杠问题 — 与
rootPath === "/"拼接后产生/path。需规范化为空字符串。//path - 硬编码区域路径 — 使用或
/us/而非动态/br/。当绑定配置变更时会失效。rootPath - 后端忽略请求头 — Node服务未读取就构建URL,导致多绑定中生成错误的规范URL。
x-vtex-root-path - 缺少Vary请求头 — 响应未设置和
Vary: x-vtex-root-path,导致CDN为不同绑定提供相同的缓存响应。Vary: x-forwarded-host
Review checklist
审核清单
- Does the app have a middleware that reads
prepareintox-vtex-root-path(backend) or usectx.state.rootPath(frontend)?useRuntime() - Does the response set and
Vary: x-vtex-root-pathso CDN caches per binding?Vary: x-forwarded-host - Are all generated URLs (links, redirects, canonicals, sitemaps) prefixed with ?
rootPath - Is normalized to
rootPath === "/"to avoid double slashes?"" - Are there no hardcoded locale path prefixes (e.g. ,
/us/)?/br/ - Does the app work correctly in both single-binding and multi-binding stores?
- 应用是否有中间件,将
prepare读取到x-vtex-root-path(后端)或使用ctx.state.rootPath(前端)?useRuntime() - 响应是否设置和
Vary: x-vtex-root-path,以便CDN针对每个绑定缓存?Vary: x-forwarded-host - 所有生成的URL(链接、重定向、规范URL、站点地图)是否都添加了前缀?
rootPath - 是否将规范化为
rootPath === "/"以避免双斜杠?"" - 是否没有硬编码的区域路径前缀(例如、
/us/)?/br/ - 应用在单绑定和多绑定商店中是否都能正常运行?
Related skills
相关技能
- vtex-io-service-paths-and-cdn — Route prefixes and CDN behavior
- vtex-io-service-apps — Backend middleware patterns
- vtex-io-react-apps — Frontend component patterns
- vtex-io-service-paths-and-cdn — 路由前缀与CDN行为
- vtex-io-service-apps — 后端中间件模式
- vtex-io-react-apps — 前端组件模式
Reference
参考资料
- Cross-Border Store Content Internationalization — Multi-binding setup for cross-border stores
- Service Path Patterns — Public, segment, and private path prefixes
- App Development — VTEX IO app development hub
- Cross-Border Store Content Internationalization — 跨境商店的多绑定设置
- Service Path Patterns — 公共、分段和私有路径前缀
- App Development — VTEX IO应用开发中心",