mcpField
V
vtex-io-rootpath

vtex-io-rootpath

github.com/vtex开发47743 次安装2 周前更新
代码仓库

关于 vtex-io-rootpath

面向 VTEX IO 多 binding 商店的开发技能,帮助应用正确处理带路径前缀的 URL 和资源。提供后端与前端的 rootPath 用法、规范化及检查规则,适用于跨境商店等场景。

主要功能

  1. 01判断多 binding 配置是否需要 rootPath
  2. 02通过后端请求头读取并清理 rootPath
  3. 03通过 useRuntime().rootPath 获取前端前缀
  4. 04为生成的 URL 添加 binding 路径前缀
  5. 05设置 Vary 响应头以区分缓存

适用场景

  1. 01修复多 binding 商店中的错误链接或 404
  2. 02生成包含路径前缀的 sitemap 和 canonical URL
  3. 03让资源路径适用于子路径 binding
  4. 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/)?
  • 应用在单绑定和多绑定商店中是否都能正常运行?

相关技能

参考资料

评论

支持 Markdown