关于 vtex-io-rootpath
面向 VTEX IO 多 binding 商店的开发技能,帮助应用正确处理带路径前缀的 URL 和资源。提供后端与前端的 rootPath 用法、规范化及检查规则,适用于跨境商店等场景。
主要功能
- 01判断多 binding 配置是否需要 rootPath
- 02通过后端请求头读取并清理 rootPath
- 03通过 useRuntime().rootPath 获取前端前缀
- 04为生成的 URL 添加 binding 路径前缀
- 05设置 Vary 响应头以区分缓存
适用场景
- 01修复多 binding 商店中的错误链接或 404
- 02生成包含路径前缀的 sitemap 和 canonical URL
- 03让资源路径适用于子路径 binding
- 04检查应用中的硬编码地区路径和双斜杠
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vtex/ skills --skill vtex-io-rootpath安装后在 Claude Code 中输入/vtex-io-rootpath确认已加载。
详细介绍
面向多绑定店铺的 VTEX IO rootPath
此技能的适用场景
在构建必须适用于多绑定配置店铺的 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)
决策规则
- 单域名多绑定(例如
store.com/us/、store.com/br/)→ 必须使用rootPath。每个生成的 URL 都必须带上绑定的根路径前缀。 - 多域名单绑定(例如
store.us、store.com.br)→rootPath通常为空或/。URL 无需添加前缀即可正常使用,但代码仍应妥善处理rootPath(非空时使用,空时跳过)。 - 后端(Node) → 平台会在
x-vtex-root-path请求标头中发送绑定的路径前缀。你的应用需要一个前置中间件(通常名为prepare),用于读取此标头、清理其值,并将其存储在ctx.state.rootPath中。设置完成后,所有下游处理程序都应直接读取ctx.state.rootPath。该中间件还必须设置Vary: x-vtex-root-path,以确保 CDN 按绑定正确缓存。 - 前端(React) → 在组件中使用
vtex.render-runtime的useRuntime().rootPath获取当前绑定的路径前缀。 - 始终添加前缀,绝不硬编码 — 切勿硬编码
/us/这样的路径前缀。始终使用运行时提供的rootPath,以确保相同代码适用于所有绑定。
严格要求
要求:在多绑定店铺中构造 URL 时始终使用 rootPath
应用生成的每个 URL——链接、重定向、API 端点、规范 URL、站点地图条目——在店铺使用基于路径的绑定时都必须包含 rootPath 前缀。
原因 — 如果没有 rootPath,/br/ 绑定中的 /my-account/orders 链接就会指向错误的绑定(或返回 404)。站点地图中的 URL 若未添加前缀,就会将搜索引擎引向错误的语言区域,从而损害 SEO。缺少前缀的重定向会将用户发送到默认绑定,而不是他们当前所在的绑定。
检测方式 — 将 URL 构造为不带 rootPath 前缀的字符串字面量(例如 /${slug}),或者调用 navigate() 时未添加 rootPath 前缀。
正确 — 将由 prepare 中间件解析的 rootPath 添加到所有生成的路径前。
// 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}`;
// 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。
// 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>
}
要求:清理 rootPath 以避免双斜杠
当 rootPath 为 "/"(单绑定或默认绑定)时,直接使用会导致 URL 中出现双斜杠(例如 //product/shoes)。请进行规范化:如果 rootPath === "/",则将其视为空字符串 ""。
原因 — URL 中的双斜杠会导致重定向循环、规范 URL 损坏以及 SEO 惩罚。某些 CDN 层对 //path 和 /path 的处理方式不同。
检测方式 — URL 构造时将 rootPath + "/" + path 直接拼接,未检查 rootPath === "/"。
正确
// 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}`;
错误
const rootPath = ctx.get("x-vtex-root-path") || "/";
const url = `${rootPath}/${path}`; // Produces "//path" for default binding
首选模式
使用 rootPath 的 State 接口
在 State 接口中声明 rootPath 和相关绑定状态,以便所有处理程序都能以类型安全的方式访问它们:
// 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>;
}
准备中间件(从标头解析 rootPath)
在每个路由的中间件链前部接入 prepare 中间件。它会读取 x-vtex-root-path 标头,对其进行清理,解析当前绑定,并设置 Vary 标头,以便 CDN 按绑定缓存响应:
// 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);
}
下游处理程序随后可直接使用 ctx.state.rootPath,无需解析标头:
// 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}`;
// ...
}
前端工具
import { useRuntime } from "vtex.render-runtime";
function usePrefixedPath(path: string): string {
const { rootPath = "" } = useRuntime();
const prefix = rootPath === "/" ? "" : rootPath;
return `${prefix}${path.startsWith("/") ? path : `/${path}`}`;
}
从前端发起绑定感知的 API 调用
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.
常见故障模式
- 多绑定场景下链接失效 — 构造导航链接时未包含
rootPath,会将用户带到错误的绑定,或导致 404。 - 站点地图中的 URL 错误 — 站点地图生成器遗漏
rootPath,导致搜索引擎收录未带前缀的 URL,这些 URL 会解析到默认绑定。 - 双斜杠 — 将
rootPath === "/"与/path拼接会产生//path。应将其规范化为空字符串。 - 硬编码的区域路径 — 使用
/us/或/br/,而不是动态的rootPath。重新配置绑定时会失效。 - 后端忽略标头 — Node 服务构造 URL 时未读取
x-vtex-root-path,导致多绑定场景下生成错误的规范 URL。 - 缺少 Vary 标头 — 响应未设置
Vary: x-vtex-root-path和Vary: x-forwarded-host,导致 CDN 为不同绑定提供相同的缓存响应。
检查清单
- 应用是否有一个
prepare中间件,将x-vtex-root-path读取到ctx.state.rootPath(后端),或使用useRuntime()(前端)? - 响应是否设置了
Vary: x-vtex-root-path和Vary: x-forwarded-host,以便 CDN 按绑定缓存? - 所有生成的 URL(链接、重定向、规范 URL、站点地图)是否都带有
rootPath前缀? - 是否将
rootPath === "/"规范化为"",以避免双斜杠? - 是否没有硬编码的区域路径前缀(例如
/us/、/br/)? - 应用在单绑定和多绑定商店中是否都能正常运行?
相关技能
- vtex-io-service-paths-and-cdn — 路由前缀与 CDN 行为
- vtex-io-service-apps — 后端中间件模式
- vtex-io-react-apps — 前端组件模式