@nextshopkit/sdk v0.5.2
šļø NextShopKit SDK
A modern, typed, and opinionated SDK for building Shopify headless storefronts with Next.js. Skip the boilerplate. Stop wrestling with GraphQL. Start shipping faster.
š Why NextShopKit?
Building a Shopify headless store from scratch is hard. You'll run into:
- ā Complex GraphQL queries
- ā Untyped responses
- ā Confusing metafields
- ā Repeating the same code over and over
NextShopKit gives you:
- ā Prebuilt, typed functions for common operations
- ā Metafield parsing, filter handling, and cart utilities
- ā Ready for React ā use as hooks or server-side
- ā Full TypeScript support with intelligent autocomplete
- ā Built-in caching for optimal performance
š¦ Installation
npm install @nextshopkit/sdkyarn add @nextshopkit/sdkpnpm add @nextshopkit/sdkš§ Quick Setup
1. Environment Variables
Add to your .env.local:
SHOPIFY_ACCESS_TOKEN=your-storefront-access-token
SHOPIFY_STORE_DOMAIN=your-shop.myshopify.com2. Initialize Client
// lib/nextshopkit/client.ts
import { createShopifyClient } from "@nextshopkit/sdk";
const client = createShopifyClient({
shop: process.env.SHOPIFY_STORE_DOMAIN!,
token: process.env.SHOPIFY_ACCESS_TOKEN!,
apiVersion: "2025-04",
enableMemoryCache: true,
defaultCacheTtl: 300,
enableVercelCache: true,
defaultRevalidate: 60,
});
export const getProduct = async (args) => client.getProduct(args);
export const getCollection = async (args) => client.getCollection(args);
export const getSearchResult = async (args) => client.getSearchResult(args);
export default client;3. Use in Server Components
// app/product/[handle]/page.tsx
import { getProduct } from "@/lib/nextshopkit/client";
export default async function ProductPage({ params }) {
const { data, error } = await getProduct({
handle: params.handle,
customMetafields: [
{ field: "custom.warranty", type: "single_line_text" },
{ field: "custom.weight", type: "weight" },
],
});
if (error || !data) {
return <div>Product not found</div>;
}
return (
<div>
<h1>{data.title}</h1>
<p>
${data.price.amount} {data.price.currencyCode}
</p>
<div dangerouslySetInnerHTML={{ __html: data.descriptionHtml }} />
{/* Access custom metafields */}
<p>Warranty: {data.metafields.customWarranty}</p>
<p>
Weight: {data.metafields.customWeight?.value}{" "}
{data.metafields.customWeight?.unit}
</p>
</div>
);
}š§© Core Features
šļø Product Management
// Fetch single product
const { data, error } = await getProduct({
handle: "premium-t-shirt",
customMetafields: [
{ field: "custom.material", type: "single_line_text" },
{ field: "custom.care_instructions", type: "rich_text" },
],
options: {
camelizeKeys: true,
renderRichTextAsHtml: true,
},
});
// Fetch collection with filters
const collection = await getCollection({
handle: "summer-collection",
first: 20,
filters: [
{ price: { min: 10, max: 100 } },
{ available: true },
{ tag: "organic" },
],
sortKey: "PRICE",
reverse: false,
});š Advanced Search
// Search across products, collections, articles
const searchResults = await getSearchResult({
query: "organic cotton",
types: ["PRODUCT", "COLLECTION"],
first: 10,
filters: [{ price: { min: 20, max: 200 } }, { productType: "Clothing" }],
sortKey: "RELEVANCE",
});š Cart Management
// Provider setup (app/layout.tsx)
import { CartProvider } from "@nextshopkit/sdk/client";
import { createShopifyClient } from "@nextshopkit/sdk";
// Client-side client for cart operations
const cartClient = createShopifyClient({
shop: process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN!,
token: process.env.NEXT_PUBLIC_SHOPIFY_ACCESS_TOKEN!,
apiVersion: "2025-04",
});
export default function RootLayout({ children }) {
return <CartProvider client={cartClient}>{children}</CartProvider>;
}
// Use in components
import { useCart } from "@nextshopkit/sdk/client";
function AddToCartButton({ variantId }) {
const { addProducts, loading, cart } = useCart();
return (
<button
onClick={() => addProducts([{ merchandiseId: variantId, quantity: 1 }])}
disabled={loading}
>
Add to Cart ({cart?.totalQuantity || 0})
</button>
);
}š·ļø Metafields Made Easy
// Define metafields with types
const product = await getProduct({
handle: "laptop",
customMetafields: [
{ field: "custom.processor", type: "single_line_text" },
{ field: "custom.ram", type: "number_integer" },
{ field: "custom.specifications", type: "rich_text" },
{ field: "custom.warranty_document", type: "file_reference" },
],
options: {
camelizeKeys: true,
resolveFiles: true,
renderRichTextAsHtml: true,
transformMetafields: (raw, casted) => ({
...casted,
// Create computed fields
displaySpecs: `${casted.customProcessor} ⢠${casted.customRam}GB RAM`,
warrantyYears: casted.customWarrantyDocument ? "2 years" : "1 year",
}),
},
});
// Access transformed metafields
console.log(product.data.metafields.displaySpecs);
console.log(product.data.metafields.warrantyYears);šÆ Advanced Usage
Filtering & Pagination
// Advanced collection filtering
const filteredProducts = await getCollection({
handle: "electronics",
first: 12,
after: "cursor-from-previous-page",
filters: [
{ price: { min: 100, max: 1000 } },
{ available: true },
{ variantOption: { name: "Color", value: "Black" } },
{ metafield: { namespace: "custom", key: "brand", value: "Apple" } },
],
sortKey: "PRICE",
reverse: false,
customMetafields: [
{ field: "custom.brand", type: "single_line_text" },
{ field: "custom.rating", type: "rating" },
],
});Server Actions Integration
// app/actions/cart.ts
"use server";
import { getProduct } from "@/lib/nextshopkit/client";
export async function addToCartAction(
productHandle: string,
variantId: string
) {
const { data, error } = await getProduct({ handle: productHandle });
if (error || !data) {
throw new Error("Product not found");
}
// Add to cart logic here
return { success: true, product: data };
}API Routes
// app/api/products/route.ts
import { getCollection } from "@/lib/nextshopkit/client";
import { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const handle = searchParams.get("collection");
const { data, error } = await getCollection({
handle: handle || "all",
first: 20,
});
if (error) {
return Response.json({ error }, { status: 500 });
}
return Response.json({ products: data.products });
}š Security & Best Practices
š Secure Credential Usage
Use appropriate environment variables for your use case:
ā Server-side operations (recommended for most data fetching):
SHOPIFY_ACCESS_TOKEN=your-storefront-access-token
SHOPIFY_STORE_DOMAIN=your-shop.myshopify.comā Client-side operations (required for cart functionality):
NEXT_PUBLIC_SHOPIFY_ACCESS_TOKEN=your-storefront-access-token
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=your-shop.myshopify.comš”ļø Security Guidelines
For Server-side Usage:
- Use private environment variables (without
NEXT_PUBLIC_prefix) - Keep credentials secure and never expose to client bundles
- Use in Server Components, API routes, and
"use server"functions
For Client-side Usage (Cart functionality):
- Use
NEXT_PUBLIC_prefixed environment variables - Only use Storefront API tokens (never Admin API tokens)
- Ensure your Storefront API token has minimal required permissions
- Consider implementing rate limiting and request validation
āļø When to Use Each Approach
Server-side (Recommended for data fetching):
- Product pages
- Collection pages
- Search results
- Static generation
- Better performance and SEO
Client-side (Required for interactive features):
- Cart management (
CartProvider,useCart()) - Real-time inventory updates
- Interactive product configurators
- Dynamic user-specific content
š Performance Optimization
// Enable caching for better performance
const client = createShopifyClient({
shop: process.env.SHOPIFY_STORE_DOMAIN!,
token: process.env.SHOPIFY_ACCESS_TOKEN!,
apiVersion: "2025-04",
enableMemoryCache: true, // In-memory caching
defaultCacheTtl: 300, // 5 minutes
enableVercelCache: true, // Vercel ISR caching
defaultRevalidate: 60, // 1 minute revalidation
});š Available Methods
| Method | Description | Tier |
|---|---|---|
getProduct() | Fetch single product by handle/ID | Core |
getCollection() | Fetch collection with products & filters | Core |
getSearchResult() | Search products, collections, articles | Core |
getPolicies() | Fetch shop policies | Core |
| Cart Functions | Complete cart management | Core |
š PRO Tier Features
Upgrade to @nextshopkit/pro for advanced features:
- šÆ
getProductVariant()- Fetch single variant with product context - šÆ
getProductVariants()- Bulk variant fetching - šÆ
getPolicy()- Fetch specific shop policy - š Metaobjects support
- š° Blog posts & articles
- š¤ Product recommendations
- š Localization support
- š Advanced search features
š Documentation
š Why Not Hydrogen?
Hydrogen is great, but it comes with constraints:
- Built around Vite and custom tooling
- Smaller community and ecosystem
- Learning curve for teams familiar with Next.js
NextShopKit lets you:
- ā Stay in Next.js (most popular React framework)
- ā Deploy anywhere (Vercel, AWS, Cloudflare)
- ā Leverage massive ecosystem and talent pool
- ā Use familiar patterns and tooling
š¤ Contributing
We welcome contributions! Please see our Contributing Guide for details.
š License
MIT Ā© NextShopKit
š Links
Built with ā¤ļø for the Next.js and Shopify community
8 months ago
8 months ago
8 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago