@standhigher/shopify-mock
v0.1.6
Published
Local window.fetch JSON scene mocks for Shopify Next.js apps.
Readme
@standhigher/shopify-mock
Browser window.fetch interceptor for Shopify Next.js apps. Return JSON scenes in local development; unmatched requests go to the real backend. It does not mock Admin GraphQL, App Bridge, or webhooks.
Hard rules
- Intercepts browser
fetchonly (not Node, RSC, orhttp). - Put the mock entry inside the Next app directory (for example
apps/<app>/mock/). Never at the repo root. - Do not
import "@standhigher/shopify-mock/next"fromnext.config.ts. Next compiles that file to CJSrequireand the subpath fails. - Copy
src/next.tsinto the app asconfig/local-dev-mock.tsand import that local file. resolveLocalDevMockAliasargument 1 is the Next app directory (the folder that containsnext.config.ts), not the monorepo root.- Merge into the existing
next.config.ts. Do not replace the whole file. Keepturbopack.root, rewrites, and other webpack aliases. transpilePackages: ["@standhigher/shopify-mock"]only. Do not add all ofnode_modulesto webpackinclude.- Static
import "@local-dev-mock"in the earliest client shell. No dynamicimport(). - Never use a bare gitignore rule named
mock(it ignores every directory with that name).
1. Install
From the workspace root in a pnpm monorepo:
pnpm add @standhigher/shopify-mock --filter <next-app-package-name>Single-package repo: pnpm add @standhigher/shopify-mock in that app.
2. Locate the installed package
pnpm often hoists the files to the workspace root. <app>/node_modules/@standhigher/shopify-mock may not exist.
From the Next app directory:
node --input-type=module -e "
import { createRequire } from 'node:module';
import { dirname } from 'node:path';
const require = createRequire(import.meta.url);
console.log(dirname(dirname(require.resolve('@standhigher/shopify-mock'))));
"That prints PKG (the package root). You can also try:
ls node_modules/@standhigher/shopify-mock/src/next.ts \
|| ls ../../node_modules/@standhigher/shopify-mock/src/next.tsUse PKG in the copy commands below.
3. Copy the alias helper
Run from the Next app directory. Overwrite config/local-dev-mock.ts if it already exists (older copies often resolve repo-root mock/index.ts and will never load <app>/mock).
mkdir -p config
cp "$PKG/src/next.ts" config/local-dev-mock.tsThe helper resolves:
- enabled +
mock/index.tsexists →<app>/mock/index.ts - otherwise →
<app>/lib/local-dev/mock-stub.ts
4. Merge next.config.ts
Keep every existing field. Add or change only the mock pieces.
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
resolveLocalDevMockAlias,
toTurbopackResolveAlias,
} from "./config/local-dev-mock";
const projectDir = dirname(fileURLToPath(import.meta.url)); // Next app dir
const workspaceRoot = resolve(projectDir, "../.."); // monorepo only, for turbopack.root
const localDevMockAlias = resolveLocalDevMockAlias(
projectDir, // NOT workspaceRoot
undefined,
process.env.LOCAL_DEV === "1" ||
process.env.NEXT_PUBLIC_MOCK_ENABLED === "1",
);
const localDevMockTurbopackAlias = toTurbopackResolveAlias(
projectDir,
localDevMockAlias,
);
const nextConfig = {
transpilePackages: ["@standhigher/shopify-mock"],
turbopack: {
root: workspaceRoot, // keep the existing value if you already set it
resolveAlias: {
"@local-dev-mock": localDevMockTurbopackAlias,
},
},
webpack: (config) => {
config.resolve ??= {};
config.resolve.alias = {
...config.resolve.alias,
"@local-dev-mock": localDevMockAlias,
};
return config;
},
};If this app already calls resolveLocalDevMockAlias(workspaceRoot, ...), change that first argument to projectDir.
If the app has no turbopack.root, omit root. example/next.config.snippet.ts is a standalone sample, not a drop-in replacement for an existing Next config.
Enable mocks with LOCAL_DEV=1 or NEXT_PUBLIC_MOCK_ENABLED=1. Start the app's webpack dev server with those env vars set. Default port is 3000 unless PORT is set.
5. Stub
If lib/local-dev/mock-stub.ts is missing:
export {};6. Client entry
At the top of the earliest client shell:
"use client";
import "@local-dev-mock";Skip this step if that import already exists.
7. Mock tree
New app (no scene JSON yet)
From the Next app directory:
cp -R "$PKG/example/mock" ./mockResult: <app>/mock/index.ts, handlers.ts, and json/example/ping/.
The example only mocks GET /openapi/v1/mock/example. Other screens still hit the real backend until you add handlers for their fetch paths.
App that already has scene JSON
Do not overwrite mock/json/ or existing handlers.ts.
Point mock/index.ts at the package (setupMock / toRequestUrl from @standhigher/shopify-mock). Keep existing scene JSON. Optionally merge the ping handler from example/mock/handlers.ts at the front of the handlers array.
8. .gitignore
Replace a bare mock rule. Files still exist on disk; Git and many IDE trees hide them.
/mock
/apps/<app>/mockConfirm with ls <app>/mock/index.ts, not git status.
9. Tests
If config/local-dev-mock.spec.ts still expects repo-root mock/index.ts, update it to the copied helper:
- missing entry →
<projectDir>/lib/local-dev/mock-stub.ts - enabled →
<projectDir>/mock/index.ts - Turbopack alias →
./mock/index.ts
pnpm --filter <next-app-package-name> exec vitest run config/local-dev-mock.spec.ts10. Verify ping
Restart the webpack dev server after config changes.
Open a page that loads the client shell (the file with import "@local-dev-mock"). Then in the browser console:
fetch("/openapi/v1/mock/example").then((r) => r.json());Expected: data.message === "Local mock is active". Unregistered paths fall through to the real backend.
11. Local pages (tell the user the URL)
Ping passing does not mean product screens are mocked.
- Find the app's local-dev or preview routes (
pages/,app/, or whatever the project uses). - The URL is
http://localhost:<port>/<that-route>(port3000unlessPORTis set). - After you add a mock for a screen, tell the human that URL. Do not stop at “handler registered”.
A page often fires several requests. Mock the first / gate requests too; otherwise a later list or detail fetch may never run.
Add an API
- Open the local page, record Network
fetchcalls (method + pathname). Also search the app source for the API path prefix. - Mock gate / first APIs as well as the list or detail call you care about.
- Copy
mock/json/example/ping/tomock/json/<domain>/<operation>/. - Write
scenes/*.json,active-scene.ts,scene-modules.ts,_scene.ts. Explicitly import each JSON file (no directory globs). Switch scenes withACTIVE_SCENEinactive-scene.ts. - Copy
datafrom a real response or from the TypeScript types next to the fetcher. Do not invent field names. - Append match/handle in
handlers.ts. Keep ping first. Match method +url.pathname; ignore query strings unless you must branch on them. - Tell the user which local URL to open and refresh. Keep ping working.
Scene JSON envelope:
{
"code": "SUCCESS",
"message": "success",
"traceId": "example-default",
"data": {},
"status": 200
}status is optional (default 200). code, message, traceId, and data are required.
Troubleshooting
| Symptom | Likely cause |
| --- | --- |
| Cannot find module '@standhigher/shopify-mock/next' | Imported the /next subpath from next.config.ts. Copy src/next.ts instead. |
| Ping hits the real backend / 404 | Dev server missing LOCAL_DEV=1; alias still uses workspaceRoot; mock tree is at repo root; no client import "@local-dev-mock"; you opened a page that does not load the client shell. |
| Page is empty, gated, or the list request never fires | A earlier fetch on that page is not mocked (or returns a payload that blocks the rest). Mock those first. |
| JSON is mocked but the UI looks wrong | Scene data does not match the real API / nearby TypeScript types. |
| One request works but the rest of the screen fails | That page has more endpoints. Check Network and add them. |
| cp: .../node_modules/@standhigher/shopify-mock: No such file | pnpm hoisted the package. Resolve PKG from step 2. |
| git status has no mock/ | Expected after .gitignore. Use ls. |
| Helper tests fail on ../../mock/index.ts | Spec still describes the old repo-root alias. Update as in step 9. |
| Webpack compiles mock/index.ts then cannot resolve @standhigher/shopify-mock | Missing transpilePackages: ["@standhigher/shopify-mock"]. |
Checklist
- Install the package on the Next app.
- Copy
$PKG/src/next.ts→<app>/config/local-dev-mock.ts(overwrite). - Merge
transpilePackages+@local-dev-mockalias; passprojectDir. - Stub + client import (skip if present).
- Copy example mock or keep existing JSON.
- Scope
.gitignore. - Fix helper unit tests if they exist.
- Start
LOCAL_DEV=1webpack and verify ping on a client-shell page. - For each screen: mock every
fetchit makes, then tell the user the local URL to open.
Chinese
给 Shopify Next.js 应用本地开发用的浏览器 window.fetch 拦截器,按接口返回 JSON 场景;未命中的请求继续走真实后端。不 mock Admin GraphQL、App Bridge 或 webhook。
硬规则
- 只拦浏览器
fetch(不拦 Node、RSC、http)。 - Mock 入口必须在 Next 应用目录内(例如
apps/<app>/mock/),禁止放仓库根。 - 不要在
next.config.ts里import "@standhigher/shopify-mock/next"。Next 会把 config 编成 CJSrequire,子路径解析会失败。 - 把包内
src/next.ts拷到应用config/local-dev-mock.ts,从本地文件引入。 resolveLocalDevMockAlias第一参数是 Next 应用目录(含next.config.ts的目录),不是 monorepo 根。- 合并进现有
next.config.ts,不要整文件替换。保留turbopack.root、rewrites、其它 webpack alias。 - 只加
transpilePackages: ["@standhigher/shopify-mock"],不要把整个node_modules加进 webpackinclude。 - 在最早的客户端壳层静态
import "@local-dev-mock",不要动态import()。 .gitignore禁止裸规则mock(会忽略任何名为 mock 的目录)。
1. 安装
pnpm monorepo 在仓库根执行:
pnpm add @standhigher/shopify-mock --filter <next-app-package-name>单仓:在应用目录 pnpm add @standhigher/shopify-mock。
2. 定位安装后的包
pnpm 经常把包提升到仓库根。<app>/node_modules/@standhigher/shopify-mock 可能不存在。
在 Next 应用目录执行:
node --input-type=module -e "
import { createRequire } from 'node:module';
import { dirname } from 'node:path';
const require = createRequire(import.meta.url);
console.log(dirname(dirname(require.resolve('@standhigher/shopify-mock'))));
"输出即 PKG(包根目录)。也可以:
ls node_modules/@standhigher/shopify-mock/src/next.ts \
|| ls ../../node_modules/@standhigher/shopify-mock/src/next.ts后面的 cp 都用这个 PKG。
3. 拷贝 alias helper
在 Next 应用目录执行。若已有 config/local-dev-mock.ts,覆盖它(旧文件往往指向仓库根 mock/index.ts,不会加载 <app>/mock)。
mkdir -p config
cp "$PKG/src/next.ts" config/local-dev-mock.tshelper 行为:
- 已启用且存在
mock/index.ts→<应用>/mock/index.ts - 否则 →
<应用>/lib/local-dev/mock-stub.ts
4. 合并 next.config.ts
保留原有字段,只改 mock 相关部分。
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
resolveLocalDevMockAlias,
toTurbopackResolveAlias,
} from "./config/local-dev-mock";
const projectDir = dirname(fileURLToPath(import.meta.url)); // Next 应用目录
const workspaceRoot = resolve(projectDir, "../.."); // 仅 monorepo,给 turbopack.root 用
const localDevMockAlias = resolveLocalDevMockAlias(
projectDir, // 不要传 workspaceRoot
undefined,
process.env.LOCAL_DEV === "1" ||
process.env.NEXT_PUBLIC_MOCK_ENABLED === "1",
);
const localDevMockTurbopackAlias = toTurbopackResolveAlias(
projectDir,
localDevMockAlias,
);
const nextConfig = {
transpilePackages: ["@standhigher/shopify-mock"],
turbopack: {
root: workspaceRoot, // 若本来就有 turbopack.root,保持原值
resolveAlias: {
"@local-dev-mock": localDevMockTurbopackAlias,
},
},
webpack: (config) => {
config.resolve ??= {};
config.resolve.alias = {
...config.resolve.alias,
"@local-dev-mock": localDevMockAlias,
};
return config;
},
};若现有代码是 resolveLocalDevMockAlias(workspaceRoot, ...),把第一参数改成 projectDir。
没有 turbopack.root 就删掉 root。example/next.config.snippet.ts 是独立示例,不要拿去整文件替换现有 Next 配置。
启用条件:LOCAL_DEV=1 或 NEXT_PUBLIC_MOCK_ENABLED=1。用带这些环境变量的 webpack 启动应用。默认端口 3000,除非设置了 PORT。
5. stub
若没有 lib/local-dev/mock-stub.ts:
export {};6. 客户端入口
最早客户端壳层顶部:
"use client";
import "@local-dev-mock";已有该 import 则跳过。
7. Mock 目录
从零(还没有场景 JSON)
在 Next 应用目录:
cp -R "$PKG/example/mock" ./mock得到 <app>/mock/index.ts、handlers.ts、json/example/ping/。
example 只 mock GET /openapi/v1/mock/example。其它页面在补对应 fetch 路径之前仍走真实后端。
已有场景 JSON
不要覆盖 mock/json/ 和现有 handlers.ts。
mock/index.ts 改为从 @standhigher/shopify-mock 引入 setupMock / toRequestUrl。保留已有场景 JSON。可选:把 example/mock/handlers.ts 的 ping 规则并到数组最前面。
8. .gitignore
去掉裸规则 mock。文件仍在磁盘上,Git 和不少 IDE 树会隐藏。
/mock
/apps/<app>/mock用 ls <app>/mock/index.ts 确认,不要只看 git status。
9. 测试
若 config/local-dev-mock.spec.ts 仍断言仓库根 mock/index.ts,改成拷贝后 helper 的行为:
- 无入口 →
<projectDir>/lib/local-dev/mock-stub.ts - 启用 →
<projectDir>/mock/index.ts - Turbopack alias →
./mock/index.ts
pnpm --filter <next-app-package-name> exec vitest run config/local-dev-mock.spec.ts10. 验证 ping
改完 config 后重启 webpack。
打开会加载客户端壳层的页面(含 import "@local-dev-mock" 的那个)。然后在浏览器控制台:
fetch("/openapi/v1/mock/example").then((r) => r.json());期望 data.message === "Local mock is active"。未注册路径回源。
11. 本地页面(把 URL 告诉使用者)
ping 通过 不等于 产品页面已经 mock。
- 在应用里找本地开发 / 预览路由(
pages/、app/,或项目自己的入口)。 - 地址是
http://localhost:<port>/<该路由>(端口默认3000)。 - 给某个屏补完 mock 之后,必须告诉使用者这个 URL,不要只说「handler 已注册」。
一个页面常常会打 多条 请求。门闸 / 最先发出的接口也要 mock,否则后面的列表或详情 fetch 可能根本不会发。
加接口
- 打开对应本地页,看 Network 里的
fetch(method + pathname)。也可在应用源码里搜 API 路径前缀。 - 连门闸 / 最先打的接口一起 mock,不要只 mock 列表或详情。
- 复制
mock/json/example/ping/为mock/json/<domain>/<operation>/。 - 写
scenes/*.json、active-scene.ts、scene-modules.ts、_scene.ts。场景 JSON 必须显式 import。用active-scene.ts里的ACTIVE_SCENE切换场景。 data从真实响应或 fetcher 旁的 TypeScript 类型拷,不要自造字段名。handlers.ts追加 match/handle。ping 放最前。匹配 method +url.pathname,不要用整段 query,除非必须按查询参数分支。- 告诉使用者打开哪个本地 URL 并刷新。保持 ping 可用。
场景 envelope:
{
"code": "SUCCESS",
"message": "success",
"traceId": "example-default",
"data": {},
"status": 200
}status 可选,缺省 200。code / message / traceId / data 必填。
排错
| 现象 | 常见原因 |
| --- | --- |
| Cannot find module '@standhigher/shopify-mock/next' | 在 next.config.ts 里 import 了 /next 子路径。应拷贝 src/next.ts。 |
| ping 打到真实后端 / 404 | 没有 LOCAL_DEV=1;alias 仍用 workspaceRoot;mock 在仓库根;客户端没 import "@local-dev-mock";打开的页面没有加载客户端壳层。 |
| 页面空白、被拦住、或列表请求根本没发出 | 该页更早的 fetch 没 mock(或返回了拦住后续请求的数据)。先 mock 那些。 |
| JSON 有了但 UI 对不上 | 场景 data 和真实 API / 附近 TypeScript 类型不一致。 |
| 一条请求好了但整页还有失败 | 该页还有别的接口。看 Network 再补。 |
| cp: .../node_modules/@standhigher/shopify-mock: No such file | pnpm 提升了包。按第 2 步解析 PKG。 |
| git status 看不到 mock/ | gitignore 之后是正常的。用 ls。 |
| 单测期望 ../../mock/index.ts | spec 还在测旧的仓库根 alias。按第 9 步改。 |
| webpack 编了 mock/index.ts 却解析不到包 | 漏了 transpilePackages: ["@standhigher/shopify-mock"]。 |
核对清单
- 给 Next 应用安装包。
- 拷
$PKG/src/next.ts→<应用>/config/local-dev-mock.ts(覆盖)。 - 合并
transpilePackages和@local-dev-mockalias,第一参数传projectDir。 - stub + 客户端 import(已有则跳过)。
- 拷贝 example mock,或保留已有 JSON。
- 收紧
.gitignore。 - 若有 helper 单测则改掉。
- 用
LOCAL_DEV=1启动 webpack,在客户端壳层页面验证 ping。 - 每个屏幕:mock 它发出的全部
fetch,然后告诉使用者打开哪个本地 URL。
