JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 32
  • Score
    100M100P100Q58749F
  • License MIT

NextShopKit PRO SDK for Next.js + Shopify

Package Exports

  • @nextshopkit/sdk
  • @nextshopkit/sdk/client

Readme

🛍️ NextShopKit SDK

npm version TypeScript Next.js License: MIT

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/sdk
yarn add @nextshopkit/sdk
pnpm 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.com

2. 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,
});
// 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


Built with ❤️ for the Next.js and Shopify community