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

create-site-starter

v1.0.0

Published

Interactive CLI that scaffolds a production-grade Next.js App Router site with an embedded Sanity Studio, modular sections, mock-data fallback, and a pre-built legal compliance suite.

Readme

create-site-starter

An interactive CLI that scaffolds a production-grade Next.js (App Router) website wired to an embedded Sanity Studio, modular UI sections, a graceful mock-data fallback engine, and a pre-built legal compliance suite (cookie consent + auto-generated policy pages).

npx create-site-starter

Repository: https://github.com/JovanTanchevski/SanityTemplateProjects


Table of Contents


What You Get

| Layer | Included | | --- | --- | | Framework | Next.js App Router, React 19, TypeScript | | Styling | Tailwind CSS v4, CSS-variable design tokens, dark-mode aware | | Motion & Icons | Framer Motion transitions, Lucide icon set | | CMS | Sanity v3-generation Studio embedded at /studio, GROQ query layer | | Resilience | Dual-mode data engine — live Sanity content, automatic mock-data fallback | | Legal | Cookie consent banner with script gating, /privacy-policy, /terms-of-service, /cookie-policy | | Forms | Contact form endpoint ready for Web3Forms / Resend | | DX | Typed content models, ESLint config, zero-config npm run dev |

Every generated project builds and renders on the first npm run dev, with or without Sanity credentials. No white screens, no unhandled fetch errors.


Quick Start

# 1. Scaffold
npx create-site-starter

# 2. Enter the generated directory
cd my-business-site

# 3. Install (skip if the CLI already installed for you)
npm install

# 4. Run
npm run dev

Then open:


The Interactive Flow

The CLI asks six questions and nothing else:

| # | Prompt | Purpose | | --- | --- | --- | | 1 | Project name | Directory name + package.json name (kebab-cased, validated) | | 2 | Company / legal entity name | Injected into legal pages, footer, and metadata | | 3 | Contact email | Contact form recipient + legal notice address | | 4 | Layout mode | Single-Page smooth-scroll vs Multi-Page routes | | 5 | Enabled sections | Hero, About, Person Cards, Pricing, Contact (multi-select) | | 6 | Sanity credentials | Project ID + dataset, or skip for now |

Answers are written into .env.local, site.config.ts, and the generated route/navigation structure. Unselected sections are not scaffolded — you get no dead files.


Demo Mode & Fallback Architecture

The template never depends on a reachable CMS to render.

                ┌──────────────────────────────┐
   page.tsx ──► │  lib/sanity/client.ts        │
                │  getContent(query, fallback) │
                └───────────┬──────────────────┘
                            │
        NEXT_PUBLIC_DEMO_MODE=true ──────────► mockData.ts
                            │
                       live fetch
                            │
              ┌─────────────┴─────────────┐
              │                           │
        success + non-empty          error / empty / timeout
              │                           │
          Sanity content            mockData.ts  (+ console warning)

How demo mode is triggered:

  1. The user skips the Sanity step in the CLI, or
  2. The credential validation request to https://<projectId>.api.sanity.io fails.

In both cases the CLI writes NEXT_PUBLIC_DEMO_MODE=true into .env.local and prints a non-blocking warning — generation always completes successfully.

Runtime fallback is independent of the CLI: even with valid credentials, any query that throws, times out, or returns an empty result silently falls back to lib/sanity/mockData.ts so a half-populated dataset can never blank a page.

To go live later, set real credentials and flip the flag:

NEXT_PUBLIC_DEMO_MODE=false

Connecting Sanity

You can connect during generation or at any point afterwards.

1. Create a project

npx sanity@latest login
npx sanity@latest projects create

Or use https://www.sanity.io/manage and copy the Project ID plus a dataset name (production by default).

2. Fill in .env.local

NEXT_PUBLIC_SANITY_PROJECT_ID=abc12xyz
NEXT_PUBLIC_SANITY_DATASET=production
NEXT_PUBLIC_SANITY_API_VERSION=2026-01-01
NEXT_PUBLIC_DEMO_MODE=false
SANITY_API_READ_TOKEN=

3. Allow the Studio origin

In https://www.sanity.io/manage → API → CORS origins, add http://localhost:3000 (and your production domain) with credentials allowed.

4. Publish your first documents

Visit /studio, fill in the Hero, About, Pricing, and Contact singletons plus any Person documents, and hit Publish. The front end picks them up on the next request; anything left unpublished keeps rendering its mock counterpart.


Single-Page vs Multi-Page Layout

The choice is stored once in site.config.ts and drives navigation, routing, and scroll behaviour:

export const siteConfig = {
  layoutMode: "single" as "single" | "multi",
  // ...
};

| | Single-Page | Multi-Page | | --- | --- | --- | | Navigation links | #about, #pricing, #contact | /about, /pricing, /contact | | Scroll behaviour | Smooth anchor scrolling with header offset | Standard route transitions | | Section rendering | All enabled sections stacked on / | One section per dedicated route | | Best for | Landing pages, single-offer businesses | Content-rich sites, SEO per topic |

Switching modes after generation is a one-line change — the header, footer, and section wrappers all read from the same flag.


Modular Sections

| Section | Component | Sanity type | | --- | --- | --- | | Hero | components/sections/HeroSection.tsx | hero | | About | components/sections/AboutSection.tsx | about | | Team / Person Cards | components/sections/PersonCardsSection.tsx | person | | Pricing | components/sections/PricingSection.tsx | pricing | | Contact | components/sections/ContactSection.tsx | contact |

Each section is a self-contained server component that receives typed content and renders motion-enhanced client children. Adding a section back after generation means restoring its component, its schema entry, and one nav item — no framework surgery.


Legal Compliance Suite

Generated sites ship with consent handling wired in, not bolted on.

Cookie banner — components/legal/CookieBanner.tsx

  • Appears on first visit only.
  • Three actions: Accept All, Essential Only, Preferences (granular per-category toggles).
  • Persists the decision in localStorage under cookie-consent.
  • Broadcasts a cookie-consent-change event so analytics/marketing scripts can mount only after consent.
  • Non-essential scripts stay unloaded until the matching category is granted.

Auto-generated policy pages — app/(legal)/

| Route | Contents | | --- | --- | | /privacy-policy | Data controller identity, lawful basis, retention, data subject rights, contact channel | | /terms-of-service | Usage terms, IP ownership, warranty and liability disclaimers, governing law placeholder | | /cookie-policy | Itemised essential vs. optional cookie table, consent withdrawal instructions |

Every page is auto-filled with the company legal entity name and contact email captured by the CLI, plus a generated "last updated" date.

Not legal advice. These documents are a solid, structured starting point that removes the blank-page problem. Have counsel review them against your jurisdiction and actual data processing before launch.


Local Development

Inside a generated project:

npm run dev        # Next.js dev server + Studio at /studio
npm run build      # Production build
npm run start      # Serve the production build
npm run lint       # ESLint
npm run typecheck  # TypeScript, no emit

Working on the generator itself:

git clone https://github.com/JovanTanchevski/SanityTemplateProjects.git
cd SanityTemplateProjects
npm install
node bin/index.mjs          # run the CLI from source

Generated Project Architecture

my-business-site/
├── app/
│   ├── (site)/                         # public pages, wrapped in header + footer
│   │   ├── (legal)/
│   │   │   ├── cookie-policy/page.tsx
│   │   │   ├── privacy-policy/page.tsx
│   │   │   └── terms-of-service/page.tsx
│   │   ├── about/page.tsx              # multi-page mode only
│   │   ├── contact/page.tsx            # multi-page mode only
│   │   ├── pricing/page.tsx            # multi-page mode only
│   │   ├── layout.tsx                  # site chrome
│   │   └── page.tsx                    # "/"
│   ├── studio/[[...tool]]/page.tsx     # embedded Sanity Studio, full viewport
│   ├── globals.css
│   └── layout.tsx                      # root: html, body, metadata
├── components/
│   ├── legal/CookieBanner.tsx
│   ├── sections/
│   │   ├── AboutSection.tsx
│   │   ├── ContactSection.tsx
│   │   ├── HeroSection.tsx
│   │   ├── PersonCardsSection.tsx
│   │   └── PricingSection.tsx
│   ├── Footer.tsx
│   └── Header.tsx
├── lib/
│   ├── sanity/
│   │   ├── client.ts                   # dual-mode data engine
│   │   ├── image.ts
│   │   ├── mockData.ts
│   │   └── queries.ts                  # GROQ
│   └── utils.ts
├── sanity/
│   └── schemas/{hero,about,person,pricing,contact,index}.ts
├── sanity.config.ts
├── site.config.ts                      # CLI answers land here
├── .env.local
└── package.json

Environment Variables

| Variable | Required | Description | | --- | --- | --- | | NEXT_PUBLIC_SANITY_PROJECT_ID | For live mode | Sanity project ID from sanity.io/manage | | NEXT_PUBLIC_SANITY_DATASET | For live mode | Dataset name, usually production | | NEXT_PUBLIC_SANITY_API_VERSION | Yes | Pinned API date, e.g. 2026-01-01 | | NEXT_PUBLIC_DEMO_MODE | Yes | true forces mock data and bypasses all network calls | | NEXT_PUBLIC_SITE_URL | Recommended | Canonical URL for metadata and Open Graph tags | | SANITY_API_READ_TOKEN | Optional | Server-only token for private datasets and drafts | | RESEND_API_KEY | Optional | Enables contact-form delivery via Resend | | WEB3FORMS_ACCESS_KEY | Optional | Enables contact-form delivery via Web3Forms | | CONTACT_TO_EMAIL | Optional | Overrides the recipient (defaults to the CLI contact email) | | CONTACT_FROM_EMAIL | Optional | Verified sender address, Resend only |

With neither mail provider configured, /api/contact validates and accepts submissions but reports honestly that delivery is not wired up — it never silently drops a message.

.env.local is git-ignored in generated projects. Only NEXT_PUBLIC_* values reach the browser — never put a write token in one.


Deployment

The template is a stock Next.js app and deploys anywhere Next runs. On Vercel:

  1. Push the generated project to a Git repository and import it.
  2. Add every variable from .env.local to the project's environment settings.
  3. Set NEXT_PUBLIC_DEMO_MODE=false once real content is published.
  4. Add the production domain to Sanity's CORS origins so /studio works in production.

Repository Layout & Contributing

This repository contains the generator, not a generated site:

SanityTemplateProjects/
├── bin/index.mjs          # CLI entry point (@clack/prompts)
├── templates/default/     # the scaffolded template source
├── package.json
└── README.md

The template stack (sanity, next-sanity, @sanity/image-url, lucide-react, framer-motion) is mirrored in this package's devDependencies as the single source of truth for the versions written into generated projects. The CLI itself ships only @clack/prompts, chalk, and execa as runtime dependencies, so npx create-site-starter stays fast.

Issues and pull requests: https://github.com/JovanTanchevski/SanityTemplateProjects/issues


Tech Stack & Versions

| Package | Version | | --- | --- | | next | ^16.3.1 | | react / react-dom | ^19.2.8 | | typescript | ^6.0.3 | | tailwindcss | ^4.3.3 | | sanity | ^6.9.2 | | next-sanity | ^13.3.3 | | @sanity/client | ^7.26.2 | | @sanity/image-url | ^2.1.1 | | framer-motion | ^13.1.0 | | lucide-react | ^1.31.0 | | eslint | ^9.39.5 | | @clack/prompts | ^1.7.0 |

Requires Node.js >= 20.9.0.

TypeScript is pinned to the 6.x line and ESLint to 9.x on purpose: eslint-config-next bundles typescript-eslint and eslint-plugin-react, which do not yet support TypeScript 7 or ESLint 10. Generated projects therefore get a npm run lint and npm run typecheck that both pass out of the box.


Troubleshooting

/studio shows a CORS error — add your origin under sanity.io/manage → API → CORS origins with credentials allowed.

The site shows placeholder copy after connecting Sanity — NEXT_PUBLIC_DEMO_MODE is still true, or the documents exist as drafts only. Publish them and restart the dev server; NEXT_PUBLIC_* variables are inlined at build time.

Configuration must contain 'projectId' — .env.local is missing or the dev server was started before it existed. Restart after saving the file.

Sections are missing — they were deselected during generation. Restore the component under components/sections/, register its schema, and add the nav entry.


License

MIT © Jovan Tanchevski — see LICENSE.