Integrations
August 4, 2026 · View on GitHub
Third-party service integrations. All are optional—remove unused ones with bun run setup:project.
Validation
All integrations use Zod schemas for environment variable validation. The integration registry (lib/integrations/registry.ts) is the single source of truth:
import { isConfigured } from '@/integrations/registry'
if (isConfigured('sanity')) {
// Sanity env vars are valid
}
Typed environment access is available via:
import { env } from '@/lib/env'
const domain = env.SHOPIFY_STORE_DOMAIN // string | undefined with IntelliSense
Available Integrations
| Integration | Purpose | Documentation |
|---|---|---|
| Sanity | Headless CMS | Visual editing, content management |
| Shopify | E-commerce | Cart, products, checkout |
| HubSpot | Forms | Marketing forms, CRM |
| Mailchimp | Newsletter | Email subscriptions |
| Turnstile | Spam protection | Cloudflare Turnstile CAPTCHA for form actions¹ |
¹ Turnstile ships with every preset, including Blank — it has no INTEGRATION_BUNDLES entry, so setup:project's --keep/--preset selection can't strip it. See Removing Integrations below for a manual recipe.
Environment Variables
# Sanity CMS
NEXT_PUBLIC_SANITY_PROJECT_ID="your-project-id"
NEXT_PUBLIC_SANITY_DATASET="production"
SANITY_API_WRITE_TOKEN="your-write-token"
# Shopify
SHOPIFY_STORE_DOMAIN="your-store.myshopify.com"
SHOPIFY_STOREFRONT_ACCESS_TOKEN="your-token"
SHOPIFY_REVALIDATION_SECRET="your-secret"
# HubSpot
HUBSPOT_ACCESS_TOKEN=your-token
NEXT_PUBLIC_HUBSPOT_PORTAL_ID=your-portal-id
# Mailchimp
MAILCHIMP_API_KEY=your-api-key
MAILCHIMP_SERVER_PREFIX=us1
MAILCHIMP_AUDIENCE_ID=your-audience-id
# Cloudflare Turnstile (spam protection)
NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=your-site-key
CLOUDFLARE_TURNSTILE_SECRET_KEY=your-secret-key
# Analytics
NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID=GTM-XXXXXX
NEXT_PUBLIC_GOOGLE_ANALYTICS=G-XXXXXXXXXX
Quick Usage
// Sanity
import { sanityFetch } from '@/integrations/sanity/live'
import { RichText } from '@/integrations/sanity/components/rich-text'
const { data } = await sanityFetch({ query: pageQuery })
// Shopify
import { Cart, AddToCart } from '@/integrations/shopify/cart'
;<Cart>
<AddToCart product={product} />
</Cart>
// HubSpot
import { EmbedHubspotForm } from '@/integrations/hubspot/embed'
;<EmbedHubspotForm formId="your-form-id" />
// Mailchimp
import { mailchimpSubscriptionAction } from '@/integrations/mailchimp'
;<Form action={mailchimpSubscriptionAction}>...</Form>
Removing Integrations
Run bun run setup:project for interactive removal. It is also drivable non-interactively (CI): --preset <key> or --keep <id,id,...> selects the integration set, --yes confirms it, --clean-homepage swaps in a blank starter homepage, and --skip-install skips the lockfile update. Keeping an integration also keeps whatever it requires (e.g. keeping theatre keeps webgl). When setup completes it removes its own machinery from the project (the setup script and its test suite) — generate, doctor, and dev stay.
Turnstile is not part of this automated flow — it has no bundle for setup:project to remove, so it ships regardless of preset or --keep selection. Remove it manually with the recipe below.
Or remove one manually:
# Sanity (~150-200KB savings)
rm -rf lib/integrations/sanity
bun remove @sanity/asset-utils @sanity/image-url next-sanity sanity
# Shopify (~50-80KB)
rm -rf lib/integrations/shopify
# HubSpot (~30-50KB)
rm -rf lib/integrations/hubspot
bun remove @hubspot/api-client
# Mailchimp (~20KB)
rm -rf lib/integrations/mailchimp
# Turnstile (no package deps — server-only fetch call, unused unless a form wires it in)
rm -rf lib/integrations/turnstile
After removal: bun run lint:fix && bun run build
Adding a New Integration
- Create Zod env schema in
lib/utils/validation.ts - Add entry to
lib/integrations/registry.ts - Declare the integration's browser-visible origins in that entry's
cspSources(types inlib/integrations/registry.ts).lib/integrations/csp.tscomposes the enforced CSP from these — a missing declaration means the integration's remote scripts, images, or requests get blocked in production. - Create integration directory under
lib/integrations/ - Add env vars to
.env.exampleandlib/env.ts