shop
AGENTS.md127 starsChanged 21 days agoMITView on GitHub
Vercel Shop is a Next.js storefront for Shopify, deployed to Vercel, with agent skills in .agents/skills/. Every feature belongs to exactly one owner. If a change cannot name its owner, or reaches into another owner's territory, redesign it. Place new work by owner first, then by domain. Docs and skills describe the product in these terms. Shopify owns commerce through the framework-agnostic Hydrogen preview SDK, not the Hydrogen React Router framework. Next.js owns the app. Eve owns the agent.
- Reads credentials
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository8 other files it gives its agents.
- build-shopSkill
- enable-analyticsSkill
- enable-i18nSkill
- enable-shopify-marketsSkill
- enable-shopify-menusSkill
- init-vercel-shopSkill
- shopify-graphql-referenceSkill
- update-shopSkill
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
20 rules. About 0 are common, 6 are distinctive, and 14 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
14
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 7
Every feature belongs to exactly one owner.
- Distinctive: line 13
Must not be reimplemented: prices, availability, currency, cart totals, and customer identity always come from Shopify responses.
- Very specific: line 19
Must not: mutate the cart through Server Actions, refresh tokens in Server Components, invalidate public caches for cart changes, or reach …
- Very specific: line 25
Must not: import Next.js request or cache APIs, add a second tool-name dispatcher, recreate a chat API route, or let model arguments select …
- Distinctive: line 36
Commerce facts come from Shopify: Never map locale to currency, hand-write Shopify response types, or guess schema fields.
- Very specific: line 39
Server Components read auth; they never refresh it: Use isCustomerLoggedIn() for UI state, requireCustomerSession() for route gates, and …
- Very specific: line 40
components/ui/takes primitive props only: No domain types, SDK types, or content helpers. - Distinctive: line 57
Read the installed Hydrogen README and bundled skills before changing an SDK integration.
- Very specific: line 65
Use the API-specific shopify-ai-toolkit skill for API facts and validation before adding or changing a document, then …
- Very specific: line 66
Write documents with gql() from @shopify/hydrogen or @shopify/hydrogen/customer-account, compose fragments through its second argument, and …
- Distinctive: line 86
Start independent work together.
- Distinctive: line 87
Keep stable headings, primary media, and likely LCP content in the static shell.
- Very specific: line 93
Every image uses next/image with reserved dimensions and sizes="100vw", including thumbnails: deviceSizes: [1320, 1920] ships two assets …
- Very specific: line 96
Read shopConfig from lib/config/index.ts directly in server and client code.
- Very specific: line 97
Keep Portless out of package.json.
- Very specific: line 99
Use the build-shop skill for route-specific workflow and audits.
- Very specific: line 124
No barrel files: never an index.ts that only re-exports.
- Very specific: line 129
Keep sub-components in their consumer's file when they share a directive, unless the file gets unwieldy.
- Very specific: line 152
Prefer scale utilities (h-18, top-1/2) to arbitrary values, which are for off-scale values such as calc() or grid templates.
- Very specific: line 153
Prefer data-[attr=value] selectors to conditional classes.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Vercel Shop
Vercel Shop is a Next.js storefront for Shopify, deployed to Vercel, with agent skills in .agents/skills/.
Three owners
Every feature belongs to exactly one owner. If a change cannot name its owner, or reaches into another owner's territory, redesign it. Place new work by owner first, then by domain. Docs and skills describe the product in these terms.
Shopify owns commerce through the framework-agnostic Hydrogen preview SDK, not the Hydrogen React Router framework.
- Owns: Storefront and Customer Account API clients, catalog, cart state and mutations, checkout, predictive search, Customer Account OAuth and sessions, policies, Shopify analytics.
- Lives in:
lib/shopify/**,lib/cart/**,lib/auth/{request,server}.ts, the handlers registered inproxy.ts, andlib/<domain>/types.tsfor the domain models transforms produce. - Must not be reimplemented: prices, availability, currency, cart totals, and customer identity always come from Shopify responses.
Next.js owns the app.
- Owns: routing and clean URLs, Server Components, caching and tag invalidation, metadata and SEO, Suspense and loading geometry, the request boundary that adapts Hydrogen's handlers, and the answer-engine surface:
text/markdownnegotiation, Markdown representations,/llms.txt, Schema.org data, sitemap, and robots. - Lives in:
app/,components/,lib/<domain>/{index,server,client,action}.ts,lib/config/,next.config.ts,proxy.ts; the answer-engine surface inlib/markdown/**,lib/seo/**,app/md/**,app/llms.txt/,app/sitemap*,app/robots.ts,components/schema/. - Must not: mutate the cart through Server Actions, refresh tokens in Server Components, invalidate public caches for cart changes, or reach into
agent/.
Eve owns the agent.
- Owns: sessions, channels, tools, connections, instructions, model selection,
/eve/v1/*. - Lives in:
agent/(tools inagent/tools/, shared execution helpers inagent/lib/),lib/agent/,components/agent/, and two Next.js routes:app/api/agent/session, which prepares the browser cart, andapp/agent/ucp-profile.json, which serves the agent's UCP profile. - Must not: import Next.js request or cache APIs, add a second tool-name dispatcher, recreate a chat API route, or let model arguments select the cart.
Describe the product, not the change
- Code, comments, docs, skills, commits, and pull requests describe current behavior, not the work that produced it.
- Pull requests are terse: what the shopper, merchant, or agent can now do, which owner it belongs to, and how it was verified.
- Docs add a detail only when a reader needs it to act or decide.
- Names and file placement carry the architecture. If a reader needs prose to find a module's owner, fix the name or location.
Boundary rules
- Commerce facts come from Shopify. Never map locale to currency, hand-write Shopify response types, or guess schema fields.
- Cart writes go through Hydrogen handlers.
proxy.tsserves/api/cart; components useuseProductFormanduseCartForm; Eve cart tools call the handlers with the cart bound from the browser cookie. No Server Actions or cache-tag invalidation for carts. - Shopify fetches, Next.js caches.
lib/shopify/operations/**/server.tsexports uncachedfetch*operations and never importsnext/cache.lib/<domain>/server.tsexportsget*wrappers that own"use cache",cacheLife, andcacheTag. Pages callget*; Eve tools callfetch*. Of browse results, only the default first page of a collection and of/collections/allis cached, throughgetCollectionResultsandgetAllProductsResultswithcacheLife("max")and webhook tags. Filtered and sorted browse, Load more pages, search results, and their facets callfetch*: cached cursor pages drift apart and duplicate products, and Search & Discovery edits send no webhook. - Server Components read auth; they never refresh it. Use
isCustomerLoggedIn()for UI state,requireCustomerSession()for route gates, andrequireCustomerAccessToken()immediately before Customer Account API calls. components/ui/takes primitive props only. No domain types, SDK types, or content helpers; wrappers incomponents/<domain>/supply labels and data.- Copy is inline and server-first. Keep labels beside their component; reusable content functionality goes in
lib/content/index.ts, not a string catalog. Pass primitive labels to client leaves, never content functions. Do not add at()runtime or next-intl to the default storefront; an already localized installation keeps next-intl, aligned catalogs, and narrowly scopedNextIntlClientProviderboundaries. - Two outward-facing agent surfaces, two owners. Next.js describes the storefront through Markdown,
/llms.txt, structured data, and the sitemap, built from the same domain types as the HTML pages. Shopify's agent endpoints (/api/mcp,/api/ucp/mcp,/.well-known/ucp) are Shopify's;proxy.tsonly forwards them. Do not reimplement either side in the other, or route Eve through the Markdown surface. - Every user-configurable
process.env.Xread has a row in.env.examplewith a short comment on when to set it, and everyprocess.env.Xread is listed inturbo.jsonglobalEnv; Turborepo's strict mode hides undeclared variables and leaves them out of cache keys.
Agent folders hold only .agents/skills/ and its .claude/skills symlink; edit skills only in .agents/skills/. Refer to skills by plain name, such as enable-shopify-markets. shopify-ai-toolkit is separate external tooling that Shop skills do not install.
<!-- BEGIN:nextjs-agent-rules -->This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->Shopify layer
Read the installed Hydrogen README and bundled skills before changing an SDK integration. Do not apply React Router loaders, actions, or Oxygen setup.
- Presentation uses domain models, never raw Shopify responses.
- Render Shopify HTML (
body,contentHtml,descriptionHtml) only after itsfetch*operation passes it throughsanitizeShopifyHtmlfromlib/shopify/html/server.ts, under anoxlint-disable-next-line react/no-danger -- … pass through sanitizeShopifyHtml when fetched.directive. storefront.requestinjects$country: CountryCode/$language: LanguageCodefrom its optionallocale: { country, language }commerce context, defaulting toshopConfig.localization; this is not the formatting locale string. Same-named variables of other types are the caller's to pass.
GraphQL
- Use the API-specific
shopify-ai-toolkitskill for API facts and validation before adding or changing a document, thenshopify-graphql-referencefor placement. Without it, use official Shopify documentation and validation tooling; never guess. - Write documents with
gql()from@shopify/hydrogenor@shopify/hydrogen/customer-account, compose fragments through its second argument, and derive response types withResultOf<typeof DOC>. Keep the#graphqlor/* GraphQL */marker. - Type inference is not schema validation. Do not run
hydrogen gql check; it needs the TypeScript compiler API, which TypeScript 7 does not ship. Do not add repo-local schema snapshots.
Cart
seedCartDatashares a per-request promise, not a data-cache entry. Keep carts out of public caches.prepareCheckoutActionreads the confirmed checkout URL; it does not mutate the cart.lib/cart/types.tsderives cart types from Hydrogen's handlers, the deliberate exception to domain types. Cart integration components may use Hydrogen store and form types;components/ui/may not.
Customer accounts
Opt-in through shopConfig.auth.isEnabled, read directly in server and client code. Enabling it requires CUSTOMER_ACCOUNT_SESSION_SECRET, an app-generated secret that is not the Shopify client secret, plus SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_ID and SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_SECRET; all three are server-only.
- Token refresh happens only where Hydrogen can commit cookies:
requireCustomerAccessToken()redirects refreshable sessions through/account/refresh. components/nav/account.tsxreads session state inside Suspense with a fixedsize-5icon container.- Sign-out is a same-origin POST so Hydrogen can clear the session and perform Shopify RP-initiated logout.
Next.js layer
- Preserve route-level data loading, promise boundaries, cache directives, invalidation tags, metadata, redirects, and auth gates when rebuilding presentation unless the task changes behavior.
- Start independent work together; block rendering only where one result needs another.
- Keep stable headings, primary media, and likely LCP content in the static shell. Push request-time inputs to the smallest Suspense boundary that needs them, with fallbacks that match the resolved geometry.
- Routes with runtime params (products, collections, blogs, articles, pages, policies) render a synchronous page that wraps the async content in
Suspensewith a route skeleton. Do not useloading.tsx, which serializes the skeleton twice in every document's RSC payload, orexport const instant = false. Export the skeleton beside the component it mirrors and mark its rootaria-busy="true". - The default view of a collection and of
/collections/allships its cached products in the static shell and per-link prefetches;proxy.tsrewrites filtered and sorted URLs to the request-timerefineroute beside it and returns 404 for directrefinerequests. KeepSearchParamsProbeon the default page, or the router reuses it for every search string and the rewrite never runs; keep the browse store, toolbar, and filter sheet inCollectionLayout, which both routes share, and each page's results insideCollectionBrowseBoundary, so a filter keeps the toolbar and open sheet and only the grid can fall back. Build browse URLs from the public path, neverusePathname(). - The root layout's
ensureStatic = "prefetch"keeps the App Shell and per-link prefetches static. Keep it at the root; do not addnavigation()orprefetch()per subtree. - Server Components are the default; isolate state, effects, browser APIs, and handlers in leaf client components.
- Server Actions are public endpoints, and TypeScript does not check their arguments at runtime. Each
action.tsexport parses every argument with a module-privatezodschema,z.strictObjectfor objects as in Eve tools, before any other work, and returns its usual failure result when parsing fails. - Every image uses
next/imagewith reserved dimensions andsizes="100vw", including thumbnails:deviceSizes: [1320, 1920]ships two assets that every surface reuses. Do not tunesizesor add widths. Preload only the LCP image; keep product grids lazy below the first row. - Link internally with
Linkfrom@/components/ui/link, nevernext/link. It upgrades to the per-link prefetch only on intent, so grids never prefetch every card. Any other prefetch change is a production-measured traffic-versus-latency choice. - The default is one deployment with clean unprefixed URLs, inline copy, and
shopConfig.localization{ country: "US", language: "EN", locale: "en-US" }: country and language set the Shopify context, locale sets formatting. It has no next-intl,lib/i18n/, orgetLocale(); keep operation locale arguments and cache inputs anyway. The i18n and Markets skills start from this baseline and preserve localized installations. - Read
shopConfigfromlib/config/index.tsdirectly in server and client code. - Keep Portless out of
package.json;pnpm dev:portlessuses a global install.
Use the build-shop skill for route-specific workflow and audits.
Eve layer
- Public traffic. Every message can incur model charges. Mitigate at the edge: BotID through
shopConfig.botid, plus deployment rate limits and AI Gateway spending controls. Do not add per-session token caps or shrinkmodelContextWindowTokens; they cut conversations short, and compaction already bounds context. Eve's default session budget and the panel'ssession-limithandling are the last resort. - One search path. Product discovery is Shopify's catalog search (
search_catalogon/api/ucp/mcp), whichsearch-productscalls throughfetchCatalogSearchandpresent-productsrenders; do not add a Storefront API search tool or a catalog MCP connection. Search identifies the agent with the UCP profile at/agent/ucp-profile.json; local and preview environments send Shopify's example profile. - Cart ownership. Cart tools bind the cart from the browser cookie through channel auth context, and the browser refreshes Hydrogen only after turns settle. The channel has no per-user authorization. Never write cart IDs or replay mutations from restored messages. Interrupted writes have no deduplication; never claim exactly-once behavior or retry an uncertain mutation.
- Bounded work. Before changing framework behavior, read
node_modules/eve/docs/README.mdand the page it routes to; package-manager links can hide files from recursive searches. Stop discovery once the location, imports, and API shape are clear. Edit authored instructions for copy changes and keep the selected model unless asked. Checkpnpm exec eve registry search <query> --jsonbefore adding an integration, prefer a native integration to a custom transport, and keep the Next.js deployment throughwithEve. Shopify's catalog and policy search are the exception: MCP connections cannot shape results, so authored tools call them throughcallShopifyMcpTool. - Verify what the shopper sees. For conversation or catalog changes, run several product-search turns in one browser session against real Shopify data and confirm the cards, follow-ups, and token usage; a build or tool result is not proof. For session controls, check restoration, Stop and Clear, and failures; pending input must not look like an empty success. Debug empty responses from the full event stream, pending input, tool outputs, and usage before changing rendering, and keep model-facing catalog data compact without dropping constraints, pagination, or errors. Say which checks were mocked, local, or against a preview; keep diagnostic credentials out of source and output, and default to read-only probes.
Testing (temporary policy)
Do not add tests, test files, runners, dependencies, or scripts unless the user explicitly asks; missing coverage is not permission. Verify with existing checks such as lint, typecheck, schema validation, builds, and runtime or browser checks, run existing tests when relevant, and report what stayed unverified. A coordinated testing effort comes later.
Environment files
.env.example files hold realistic placeholders, never redaction language like [redacted]. Shopify values use their real format (your-store.myshopify.com, shp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx); everything else uses a your-…-here string.
<!-- BEGIN:vercel-shop-style -->Code Style
Ordering and exports
- Alphabetize export specifiers, destructuring patterns, interface and type properties, config object keys, i18n JSON keys, and string union members.
pnpm formatsorts imports. - No barrel files: never an
index.tsthat only re-exports. - Named exports only in component files; pages use default exports.
Files
- Keep sub-components in their consumer's file when they share a directive, unless the file gets unwieldy. Split a different directive into
foo-client.tsxorfoo-server.tsxbesidefoo.tsx. - Push
"use client"as far down as possible; pages, layouts, and data-fetching wrappers stay server components and pass promises or data to client children. lib/is organized by domain, then execution context, using only the files a domain needs:index.ts(universal, never a barrel),server.ts(kept out of client graphs),request.ts(takes theRequestorHeadersexplicitly, such as Hydrogen handlers and session cookies; whatproxy.tsand Route Handlers import; never importsnext/cache,next/headers,next/navigation, orreact, which load the App Router runtime into the proxy),client.ts(a React client boundary, not an HTTP client),action.ts("use server", verb +Actionexports), andtypes.ts(imported withimport type). Subdivide with the same filenames, such aslib/cart/gift-card/client.ts; no descriptive siblings likecart-client.ts, flat root modules, or forwarding exports.- Shopify transports live in their API domain's
server.ts; pure transforms and formatting helpers live inindex.ts. - Storefront models live in their owning domain's
types.ts; cross-domain primitives inlib/money/types.ts,lib/media/types.ts, andlib/pagination/types.ts; SDK contracts and inferred response types underlib/shopify/. Props stay beside their components, and small private types may stay local. Never import shared types fromserver.tsoraction.ts. Generated artifacts keep their generator's paths.
Naming
- Files
kebab-case.tsx, componentsPascalCase, constantsSCREAMING_SNAKE_CASE, server actions verb +Action. - Props are
interface {ComponentName}Props. Native-element pass-through usesComponentProps<"div">. - One React import per file with named imports and inline
typemodifiers (import { type ReactNode, Suspense } from "react"), orimport typewhen all are types; neverReact.*.
Spacing
Containersets horizontal padding and max width only; never putpy-*on it.Pageowns top padding (pt-10by default, overridden throughclassName) and has no bottom padding; the footer'spt-20provides that gap. Full-height layouts addflex flex-1 flex-col.- Wrap sibling sections in
Sections(gap-10by default). Inside a section, the parent spaces its children withgrid gap-*; children carry no outer margins orspace-y-*, so a component keeps its layout wherever it is placed. Padding inside components, negative-margin breakouts, and margin resets on rendered rich text are fine. - Spacing values are the storefront's design choice: reuse the values nearby code already uses, and do not change spacing outside the task.
Tailwind
- Style with utilities, not
app/globals.css. Register new values in@theme; keepglobals.cssto tokens, resets, keyframes, and shared utilities, with no global element rules. @theme inlinedoes not emit:rootvariables; outside utilities, reference the underlying variable.- Prefer scale utilities (
h-18,top-1/2) to arbitrary values, which are for off-scale values such ascalc()or grid templates. - Prefer
data-[attr=value]selectors to conditional classes; usecn()when classes must be conditional,data-slotas styling hooks, andcvafor variants. - Interactive elements use
cursor-pointer; disabled ones usecursor-not-allowed.
Comments
Default to none. A comment is one line stating a hidden constraint, an upstream workaround, a non-obvious invariant, or a cross-system quirk, and survives only if an agent would otherwise break behavior. Never restate names, narrate code, reference current work or PRs, add banners, leave a TODO without TODO(handle): reason, or write multi-line prose. Keep tooling directives and generated or license headers.
<!-- END:vercel-shop-style -->Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

