@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/sdkQuick 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:
- Open the site.
- Go to Store or Payments.
- Connect the client's Stripe account.
- 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
developmentmode, 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 --watchThe 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.
