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

@vard-app/sdk

v0.4.0

Published

Vard SDK — modern, schema-first content layer for Next.js

Readme

Vard SDK

The modern, schema-first content layer for Next.js.

Installation

npm install @vard-app/sdk

Quick Start

1. Define your client

Setup a shared client instance in your project (e.g. lib/vard.ts).

import { createVard, v } from "@vard-app/sdk";
import { createVardNextAdapter } from "@vard-app/sdk/next";

export const vard = createVard({
  apiKey: process.env.VARD_API_KEY,
  store: createVardNextAdapter(),
  schema: {
    hero: {
      title: v.string("Welcome to Vard"),
      image: v.image("/hero.jpg"),
    },
    features: v.collection({
      title: v.string(),
      icon: v.image(),
    }),
  },
});

2. Prefetch in Layout (App Router)

To ensure zero-layout shift, prefetch values in your root layout.

// app/layout.tsx
import { prefetchVardValues } from "@vard-app/sdk/next";
import { vard } from "@/lib/vard";

export default async function RootLayout({ children }) {
  await prefetchVardValues(vard);
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}

3. Use in Server Components

Retrieve your entire content structure with a single, type-safe call.

// app/page.tsx
import { vard } from "@/lib/vard"; // your local lib/vard.ts

export default async function Page() {
  const { hero, features } = await vard.get();

  return (
    <main>
      <h1>{hero.title}</h1>
      <img src={hero.image} />

      <ul>
        {features.map((f) => (
          <li key={f.title}>{f.title}</li>
        ))}
      </ul>
    </main>
  );
}

Add Analytics

Vard-hosted projects receive VARD_SITE_ID during production provisioning. Add the analytics component to the root layout; it does nothing when the variable is absent.

import { VardAnalytics } from "@vard-app/sdk/next";

<VardAnalytics siteId={process.env.VARD_SITE_ID} />;

Per-Page Schema (Co-location)

As your site grows, keeping all schema in one lib/vard.ts becomes unwieldy. Use v.schema() and v.merge() to co-locate schema with each page and aggregate in one place.

Define schema alongside each page

// app/rates/schema.ts  (or inline in app/rates/page.tsx)
import { v } from "@vard-app/sdk";

export const ratesSchema = v.schema({
  rates: {
    seo: v.seo(),
    header: {
      title: v.string("Rates & Insurance"),
    },
    fees: v.collection({
      service: v.string(),
      price: v.string(),
    }),
  },
});

Merge into a single client

// lib/vard.ts
import { createVard, v } from "@vard-app/sdk";
import { createVardNextAdapter } from "@vard-app/sdk/next";
import { ratesSchema } from "@/app/rates/schema";
import { aboutSchema } from "@/app/about/schema";
import { teamSchema } from "@/app/team/schema";

export const vard = createVard({
  apiKey: process.env.VARD_API_KEY,
  store: createVardNextAdapter(),
  schema: v.merge(ratesSchema, aboutSchema, teamSchema),
});

Use in each page

// app/rates/page.tsx
import { vard } from "@/lib/vard";

export default async function RatesPage() {
  const { rates } = await vard.get();
  return <h1>{rates.header.title}</h1>;
}

v.merge() is shallow — it combines top-level keys. If two schemas share a top-level key (e.g. both export home), the last one wins.

Global Collections (pinToSidebar)

Collections normally appear in the Collections tab of their parent page editor. For site-wide data pools not tied to a single page, use pinToSidebar: true to give them a dedicated sidebar entry:

schema: {
  // Appears in sidebar — used across multiple pages
  teamMembers: v.collection(
    { name: v.string(), photo: v.image(), bio: v.string() },
    [],
    {
      pinToSidebar: true,
      tableColumns: ["photo", "name"],
    }
  ),

  // Page-scoped — lives in the Rates page Collections tab
  rates: {
    fees: v.collection({ service: v.string(), price: v.string() }),
  },
}

Use tableColumns to choose the fields shown in the dashboard table. Fields omitted from the table remain available when adding or editing an item. Without tableColumns, the table shows every schema field in declaration order. Column keys are type-checked against the collection schema.

Singletons

A collection is a list of N homogeneous items (a table). A singleton is exactly one record of named fields — edit it as a single form, read it back as a plain object. Reach for a singleton for one-off editable blocks (an announcement banner, a footer CTA, SEO defaults) where a collection-of-one would be the wrong shape.

export const vard = createVard({
  apiKey: process.env.VARD_API_KEY,
  singletons: {
    banner: v.singleton(
      {
        visible: v.boolean(true, { label: "Show banner" }),
        text: v.string("", { label: "Announcement" }),
        linkLabel: v.string("Learn more"),
        linkUrl: v.string(""),
      },
      // initial values (optional) — seeded once, never overwrites saved data
      { visible: true, text: "Free shipping this week" },
      // options (optional)
      { pinToSidebar: true, label: "Banner" }
    ),
  },
});

Read it in a Server Component — no array indexing:

const banner = await vard.singleton("banner");

if (banner.visible && banner.text) {
  return (
    <div className="banner">
      {banner.text}
      {banner.linkUrl ? <a href={banner.linkUrl as string}>{banner.linkLabel}</a> : null}
    </div>
  );
}

With pinToSidebar: true, the singleton gets its own dashboard sidebar entry (using label) that opens its form directly — no table, no add-row.

Payments With Stripe

Vard commerce lets a client site display products and send shoppers to Stripe Checkout without putting Stripe secrets in the site code. The site's owner connects Stripe from the Vard dashboard, and the SDK uses the site's Vard ID to route checkout through that connected Stripe account.

How Stripe Connect works in Vard

  • Vard owns the platform Stripe integration and keeps the platform secret key on Vard servers.
  • Each site connects its own Stripe account in the Vard dashboard.
  • The connected Stripe account ID is stored by Vard for that site.
  • Client sites call vard.commerce.*; they do not need a Stripe secret key.
  • Checkout is created server-side by Vard and scoped to the connected Stripe account.

1. Enable commerce for the site

In the Vard dashboard:

  1. Open the site.
  2. Go to Store or Payments.
  3. Connect the client's Stripe account.
  4. Add published products with prices and inventory.

The store must be enabled and Stripe onboarding must be complete before checkout will succeed.

2. Add the site ID to the SDK client

siteId is required for commerce methods. Keep it in an environment variable so the same code can be reused across environments.

// lib/vard.ts
import { createVard, v } from "@vard-app/sdk";
import { createVardNextAdapter } from "@vard-app/sdk/next";

export const vard = createVard({
  apiKey: process.env.VARD_API_KEY,
  siteId: process.env.VARD_SITE_ID,
  store: createVardNextAdapter(),
  schema: {
    hero: {
      title: v.string("Welcome to Vard"),
    },
  },
});

3. Display products

Use vard.commerce.products() in a Server Component to fetch published, in-stock products for the site.

// app/shop/page.tsx
import { vard } from "@/lib/vard";

export default async function ShopPage() {
  const products = await vard.commerce.products();

  return (
    <main>
      <h1>Shop</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>
            <h2>{product.name}</h2>
            <p>{product.description}</p>
            <p>${(product.priceUsdCents / 100).toFixed(2)}</p>
          </li>
        ))}
      </ul>
    </main>
  );
}

4. Create checkout from a Server Action

Call vard.commerce.checkout() from server-side code only. Pass Vard product IDs, redirect the returned URL, and let Vard create the Stripe Checkout Session for the site's connected Stripe account.

// app/shop/actions.ts
"use server";

import { redirect } from "next/navigation";
import { vard } from "@/lib/vard";

export async function checkout(productId: string) {
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000";

  const { url } = await vard.commerce.checkout({
    items: [{ productId, quantity: 1 }],
    successUrl: `${siteUrl}/shop/success?session_id={CHECKOUT_SESSION_ID}`,
    cancelUrl: `${siteUrl}/shop`,
  });

  redirect(url);
}

Environment variables for commerce sites

| Variable | Description | | ---------------------- | ------------------------------------------------------------------ | | VARD_API_KEY | Your workspace API key | | VARD_SITE_ID | The Vard site UUID used by analytics and vard.commerce.* methods | | NEXT_PUBLIC_SITE_URL | Public site URL used to build checkout return URLs |

Do not add Stripe secret keys to client sites. Stripe account onboarding, connected account IDs, checkout creation, and platform fees are handled by Vard.

Schema-First Features

  • Type Safety: Full TypeScript inference out of the box. No manual interfaces required.
  • Auto-Sync: In development mode, the SDK automatically syncs your schema to the Vard dashboard.
  • Hierarchical Data: Define nested objects and collections to match your UI perfectly.
  • Backward Compatibility: Standard vard.string(key, fallback) methods still work for simple use cases.

Environment Variables

| Variable | Description | | --------------- | ---------------------------------------------------------------- | | VARD_API_KEY | Your workspace API key (required for production) | | VARD_SITE_ID | Optional site UUID, required for analytics and vard.commerce.* | | VARD_API_BASE | Optional custom API base (defaults to vard.app) |

Email Previews

Define each email in a *.email.ts module:

import { defineEmail } from "@vard-app/sdk";

export default defineEmail({
  slug: "order-confirmation",
  name: "Order Confirmation",
  group: "Orders",
  preview: () => ({
    subject: "Order confirmed",
    html: "<p>Thanks for your order.</p>",
  }),
});

Then sync every discovered template:

vard emails sync
vard emails sync --watch

The default discovery pattern is **/*.email.{ts,tsx,js,jsx,mjs,cjs}. Override it in vard.config.ts with defineConfig({ emails: { include: [...] } }).

The optional group field organizes related templates in the dashboard. Definitions without a group appear under Other when grouped definitions are present.