0.5.2 • Published 8 months ago

@nextshopkit/sdk v0.5.2

Weekly downloads
-
License
MIT
Repository
github
Last release
8 months ago

šŸ›ļø 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,
});

šŸ” 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

MethodDescriptionTier
getProduct()Fetch single product by handle/IDCore
getCollection()Fetch collection with products & filtersCore
getSearchResult()Search products, collections, articlesCore
getPolicies()Fetch shop policiesCore
Cart FunctionsComplete cart managementCore

šŸš€ 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

0.5.2

8 months ago

0.5.1

8 months ago

0.5.0

8 months ago

0.4.20

9 months ago

0.4.19

9 months ago

0.4.18

9 months ago

0.4.17

9 months ago

0.4.16

9 months ago

0.4.15

9 months ago

0.4.14

9 months ago

0.4.13

9 months ago

0.4.11

9 months ago

0.4.10

9 months ago

0.4.9

9 months ago

0.4.8

9 months ago

0.4.7

9 months ago

0.4.6

9 months ago

0.4.5

9 months ago

0.4.4

10 months ago

0.4.3

10 months ago

0.4.2

10 months ago

0.4.1

10 months ago

0.4.0

10 months ago

0.3.0

10 months ago

0.2.8

10 months ago

0.2.7

10 months ago

0.2.6

10 months ago

0.2.5

10 months ago

0.2.4

10 months ago