npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@storesynk/react

v0.1.0-beta.13

Published

Framework-agnostic React bindings for Storesynk — server-render Shopify data inside Storesynk tags from any classic-SSR React stack (TanStack Start, Remix, Vite SSR), adopted flash-free by the client runtime.

Readme

@storesynk/react

Framework-agnostic React bindings for Storesynk — server-render Shopify data inside Storesynk tags from any classic-SSR React stack (TanStack Start, Remix / React Router, vanilla Vite SSR), adopted flash-free by the client runtime. The sibling of @storesynk/next, minus the RSC coupling.

How it differs from @storesynk/next

Next's wrappers are async Server Components. In classic SSR a component renders on the server and again at hydration, so it can't be async — instead the async half (fetch + template stringify + core's server transform) runs in your framework's data layer and returns a serializable prepared object that feeds a synchronous component:

loader / createServerFn                    component (server + hydration)
  renderProduct(<Template/>, {handle}) ──▶  <StoresynkProduct prepared={...} />
  = fetch + transform + payload             = one dangerouslySetInnerHTML

Both renders paint the same prepared bytes, so hydration can never mismatch, React never reconciles the engine-mutated subtree, and the client runtime adopts the embedded payloads with zero refetch.

Setup

npm i @storesynk/react
npx @storesynk/react init     # scaffolds storesynk.config.ts

Add the credentials to your env (public Storefront token only — never Admin):

SHOPIFY_STORE_DOMAIN=your-shop.myshopify.com
SHOPIFY_STOREFRONT_ACCESS_TOKEN=...

Import the config for its side effect at the top of your root module (TanStack Start: the router/root route module; Remix: app/root.tsx). On runtimes without process.env (Cloudflare Workers without nodejs_compat), pass domain/token to configureStoresynk directly.

TanStack Start

// src/templates/product.tsx — static host elements only (no state/handlers)
export const ProductTemplate = () => (
  <>
    <h1><show-title></show-title></h1>
    <show-image main widths="400,800"></show-image>
    <change-option></change-option>
    <show-price></show-price>
    <add-to-cart>Add to cart</add-to-cart>
  </>
);

// src/routes/products.$handle.tsx
import { createFileRoute } from '@tanstack/react-router';
import { createServerFn } from '@tanstack/react-start';
import { getWebRequest } from '@tanstack/react-start/server';
import { StoresynkProduct, StoresynkStore } from '@storesynk/react';
import { createRequestScope, renderProduct, renderStore } from '@storesynk/react/server';
import { ProductTemplate } from '../templates/product';

const getPage = createServerFn({ method: 'GET' })
  .validator((handle: string) => handle)
  .handler(async ({ data: handle }) => {
    // `request` carries everything per-request: the buyer's locale cookie, the
    // URL's variant selection / listing filters, the JSON-LD canonical. Explicit
    // options (`locale`, `state`, `selectedOptions`) always win over it.
    const opts = { request: getWebRequest(), fresh: true, scope: createRequestScope() };
    return {
      store: await renderStore(opts),
      product: await renderProduct(<ProductTemplate />, { handle, ...opts }),
    };
  });

export const Route = createFileRoute('/products/$handle')({
  loader: ({ params }) => getPage({ data: params.handle }),
  component: () => {
    const { store, product } = Route.useLoaderData();
    return (
      <StoresynkStore prepared={store}>
        <StoresynkProduct prepared={product} />
      </StoresynkStore>
    );
  },
});

On document loads (SSR, reloads, shared links) getWebRequest() is the page request, so filtered collection URLs and variant links server-render exactly. On client-side navigations a server fn sees its own RPC request instead — the helpers then fall back to the default view and the client engine applies the URL state itself, which is the right division of labor anyway.

Remix / React Router

export const loader = async ({ params, request }: LoaderFunctionArgs) => {
  const opts = { request, fresh: true, scope: createRequestScope() };
  return {
    store: await renderStore(opts),
    product: await renderProduct(<ProductTemplate />, { handle: params.handle!, ...opts }),
  };
};

export default function ProductPage() {
  const { store, product } = useLoaderData<typeof loader>();
  return (
    <StoresynkStore prepared={store}>
      <StoresynkProduct prepared={product} />
    </StoresynkStore>
  );
}

What's included

| @storesynk/react/server (loaders) | @storesynk/react (components) | | --------------------------------------- | -------------------------------- | | renderStore | <StoresynkStore> | | renderProduct | <StoresynkProduct> | | renderList | <StoresynkList> | | renderCollection (URL filter/sort/page state derives from request; parseListingParams to override) | <StoresynkCollection> | | renderLocalePicker | <StoresynkLocalePicker> | | renderProductJsonLd | <StoresynkProductJsonLd> | | productSeo (head-API-neutral) | — | | getRequestLocale / getRequestCustomer (cookie/Request) | — | | checkGate (returns the decision — map it to your router's redirect/notFound) | — | | getProduct, getShop, getProductHandles, … (raw fetch helpers) | <StoresynkStatic> (no-data markup: cart drawer, client-only templates) |

Templates handed to the render helpers must be static host elements (the same tags as a plain HTML page — no client components, no state, no event handlers) or a raw HTML string. Everything the transform can't fill server-side (cart, predictive search, money/file/list metafields) hydrates client-side exactly as on a static page.

Not included (Next-specific, no framework-neutral equivalent): the server-owned cart (HttpOnly cookie id via server actions) — the default client cart (localStorage id, direct Storefront API) works everywhere.

Notes

  • Cloudflare Workers / edge: everything is fetch-based and Node-API-free; pass credentials via configureStoresynk if process.env is absent.
  • Caching: fetches are module-cached per process (build-time economy). Dynamic routes pass fresh: true plus a shared createRequestScope() so one request's helpers dedupe without leaking across requests.
  • React 18: works at runtime; the bundled JSX typings for the Storesynk tags require React 19's @types/react (React 18 consumers can keep their own JSX.IntrinsicElements declarations).
  • <StoresynkStatic> pulls react-dom/server into the client bundle (it re-stringifies at hydration); the prepared-render components don't.