
Research
/Security News
jscrambler npm Package Compromised in Supply Chain Attack
A compromised jscrambler npm release added a malicious preinstall hook that runs hidden native binaries on Linux, macOS, and Windows.
next-shopify-experimental
Advanced tools
Experimental Shopify toolkit for Next.js: a typed Storefront API client, automatic cache tagging, and webhook-driven revalidation.
A Shopify toolkit for Next.js: a typed Storefront API client with built in support for Next.js 16 Cache Components.
cacheComponents: true)npm install next-shopify-experimental
# or
pnpm add next-shopify-experimental
# or
yarn add next-shopify-experimental
# or
bun add next-shopify-experimental
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig
// lib/shopify.ts
import { createStorefrontClient } from 'next-shopify-experimental'
import { defineFetch } from 'next-shopify-experimental/cache'
// Reads SHOPIFY_STORE_DOMAIN + SHOPIFY_STOREFRONT_TOKEN from the environment.
export const { shopifyFetch } = defineFetch({
client: createStorefrontClient(),
})
createStorefrontClient() reads your store domain and access token from the environment by convention, so the common case takes no arguments. Pass any option to override its env default, and it validates up front — a missing domain or token throws a clear, actionable error instead of failing cryptically on the first request.
'use cache' boundaryshopifyFetch runs the query, then tags the surrounding cache entry with the GID of every resource it returned. Call it from a 'use cache' function:
// app/page.tsx
import { shopifyFetch } from '@/lib/shopify'
async function getShop() {
'use cache'
const { data } = await shopifyFetch<{ shop: { id: string; name: string } }>({
query: `{ shop { id name } }`,
})
return data?.shop
}
export default async function Page() {
const shop = await getShop()
return <h1>{shop?.name}</h1>
}
Add a webhook route. When Shopify reports a change, next-shopify-experimental verifies it and revalidates the matching tags:
// app/api/shopify/webhook/route.ts
export { POST } from 'next-shopify-experimental/webhook'
Then create a webhook subscription pointed at https://your-app.com/api/shopify/webhook, and set its signing secret as SHOPIFY_WEBHOOK_SECRET.
# .env.local
SHOPIFY_STORE_DOMAIN=your-shop.myshopify.com
SHOPIFY_STOREFRONT_TOKEN=your-public-storefront-access-token
SHOPIFY_WEBHOOK_SECRET=your-webhook-signing-secret
| Variable | Read by | Description |
|---|---|---|
SHOPIFY_STORE_DOMAIN | createStorefrontClient | Your store's domain. |
SHOPIFY_STOREFRONT_TOKEN | createStorefrontClient | A public Storefront API access token. |
SHOPIFY_WEBHOOK_SECRET | the webhook handler | The webhook signing secret — read automatically by POST. |
createStorefrontClient also reads SHOPIFY_STOREFRONT_PRIVATE_TOKEN (a server-only token, as an alternative to the public one) and SHOPIFY_API_VERSION (optional; otherwise a built-in default is used).
That's the whole integration: reads are cached and tagged, and a shop/update webhook busts exactly the pages that read the shop.
Every object in Shopify's API has a global ID — a GID, like gid://shopify/Shop/1. next-shopify-experimental uses it as the link between reads and writes:
shopifyFetch scans the response for GIDs and tags the cache entry with each one (shopify:shop:1).revalidateTag.Because both sides derive the tag from the same GID, a change invalidates exactly the cache entries that read that resource — with no tag bookkeeping on your part.
createStorefrontClientCreates the Storefront API client, reading configuration from the environment by convention so the common case is zero-argument. It validates up front — a missing store domain or access token throws a clear, actionable error rather than failing cryptically on the first request. Pass any option to override its env default.
import { createStorefrontClient } from 'next-shopify-experimental'
// Reads SHOPIFY_STORE_DOMAIN + SHOPIFY_STOREFRONT_TOKEN from the environment:
const client = createStorefrontClient()
// …or override any option:
const client = createStorefrontClient({ apiVersion: '2025-10' })
Every option is optional; each falls back to an environment variable:
| Option | Falls back to | Description |
|---|---|---|
storeDomain | SHOPIFY_STORE_DOMAIN | Your *.myshopify.com (or custom) domain. |
publicAccessToken | SHOPIFY_STOREFRONT_TOKEN | A public Storefront access token (safe in the browser). |
privateAccessToken | SHOPIFY_STOREFRONT_PRIVATE_TOKEN | A private Storefront access token (server only). |
apiVersion | SHOPIFY_API_VERSION (or default) | Storefront API version, e.g. '2026-04'. |
A store domain and exactly one access token are required (from config or the environment). It also accepts the client's remaining options (clientName, retries, customFetchApi, logger).
createStorefrontApiClientThe raw client factory, re-exported from @shopify/storefront-api-client (along with all of its types) unchanged — no env convention, no validation. Reach for it when you want full, unmanaged control; otherwise prefer createStorefrontClient. request() is return-based and does not throw on GraphQL errors.
import { createStorefrontApiClient } from 'next-shopify-experimental'
const client = createStorefrontApiClient({
storeDomain: 'your-shop.myshopify.com',
apiVersion: '2026-04',
publicAccessToken: process.env.SHOPIFY_STOREFRONT_TOKEN!,
})
defineFetchFrom next-shopify-experimental/cache. Binds a client to a shopifyFetch that tags reads for revalidation.
import { defineFetch } from 'next-shopify-experimental/cache'
export const { shopifyFetch } = defineFetch({ client })
| Name | Type | Default | Description |
|---|---|---|---|
client | StorefrontApiClient | — | The client returned by createStorefrontApiClient. |
debug | boolean | false | Log each fetch's collected cache tags to the server console. |
shopifyFetchReturns the client's { data, errors, extensions } response, and tags the surrounding 'use cache' entry.
const { data, errors } = await shopifyFetch<TData>({ query, variables })
| Name | Type | Default | Description |
|---|---|---|---|
query | string | — | The GraphQL operation. |
variables | object | — | Operation variables. |
cache | boolean | true | Tag this read. Set false for a dynamic, uncached read — no 'use cache' boundary needed. |
debug | boolean | inherited | Override the defineFetch debug setting for this call. |
It also accepts the underlying client's request options (apiVersion, headers, retries, signal).
Inside a
'use cache'boundary,shopifyFetchtags the entry. Called outside one withoutcache: false, it throws with a message pointing you at both fixes.
next-shopify-experimental/webhookPOSTThe convention handler — verifies with SHOPIFY_WEBHOOK_SECRET and revalidates. Re-export it as your route:
// app/api/shopify/webhook/route.ts
export { POST } from 'next-shopify-experimental/webhook'
createWebhookHandlerUse the factory when you need to configure it:
import { createWebhookHandler } from 'next-shopify-experimental/webhook'
export const POST = createWebhookHandler({
secret: process.env.SHOPIFY_WEBHOOK_SECRET,
})
| Name | Type | Default | Description |
|---|---|---|---|
secret | string | process.env.SHOPIFY_WEBHOOK_SECRET | Webhook signing secret. The handler fails closed (500) if it's missing. |
signatureVerification | boolean | true | Set false only if you verify upstream — HMAC is skipped and no secret is required. |
Responses: 200 { revalidated } for a verified webhook (including topics it doesn't tag), 401 for a missing or invalid signature, 500 if the secret isn't configured.
To build a fully custom handler, compose these directly:
| Export | Description |
|---|---|
verifyWebhook({ body, signature, secret }) | Verify a Shopify webhook signature (Web Crypto, constant-time). Resolves a boolean; never throws. |
getWebhookCacheTags(topic, payload) | Derive the cache tags for a verified payload — the same tags the read side produced. |
next-shopify-experimental tags the Shop resource today. Other resources (products, collections, …) aren't tagged yet: their data still reads normally, but no tags are emitted for them and their webhooks are acknowledged without revalidation — no errors are thrown. Coverage grows as the resource registry expands.
A runnable Next.js app lives in apps/example.
FAQs
Experimental Shopify toolkit for Next.js: a typed Storefront API client, automatic cache tagging, and webhook-driven revalidation.
We found that next-shopify-experimental demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
A compromised jscrambler npm release added a malicious preinstall hook that runs hidden native binaries on Linux, macOS, and Windows.

Research
/Security News
A malicious .NET package is typosquatting the Braintree SDK to steal live payment card data, merchant API keys, and host secrets from production apps.

Security News
/Research
Compromised Injective SDK npm version 1.20.21 exfiltrates wallet private keys and mnemonics through fake telemetry functionality.