Coding Agent Prompt
Coding Agent Prompt: Wine Café & Hi-Fi Bar Website
Section titled “Coding Agent Prompt: Wine Café & Hi-Fi Bar Website”Stack: Next.js (App Router) + Sanity v3 + Vercel
Section titled “Stack: Next.js (App Router) + Sanity v3 + Vercel”PROJECT OVERVIEW
Section titled “PROJECT OVERVIEW”You are building a modern, production-grade website for a wine café and Hi-Fi music bar. The site serves as a content-rich marketing and discovery platform with a clean, atmospheric aesthetic appropriate for a premium hospitality brand. It must be fast, SEO-optimized, easy for non-technical editors to maintain via a headless CMS, and architected to support e-commerce features (wine sales, ticketed events) in a future phase.
TECH STACK
Section titled “TECH STACK”| Layer | Technology |
|---|---|
| Frontend Framework | Next.js 14+ (App Router, React Server Components) |
| CMS | Sanity v3 (Studio embedded at /studio) |
| Language | TypeScript (strict mode) |
| Styling | Tailwind CSS v3 |
| Image Handling | Sanity image pipeline + Next.js <Image> |
| Deployment | Vercel |
| Package Manager | pnpm |
| Linting/Formatting | ESLint + Prettier |
| Future E-commerce | Stripe (stub interfaces now, implement later) |
REPOSITORY STRUCTURE
Section titled “REPOSITORY STRUCTURE”Scaffold the project with the following structure. Do not deviate from this layout without explanation.
/├── app/ # Next.js App Router│ ├── (site)/ # Public-facing site route group│ │ ├── layout.tsx # Site shell: nav, footer│ │ ├── page.tsx # Homepage│ │ ├── menu/│ │ │ └── page.tsx│ │ ├── events/│ │ │ ├── page.tsx # Events listing│ │ │ └── [slug]/page.tsx # Event detail│ │ ├── blog/│ │ │ ├── page.tsx # Blog listing│ │ │ └── [slug]/page.tsx # Blog post│ │ ├── artists/│ │ │ ├── page.tsx # Artist roster│ │ │ └── [slug]/page.tsx # Artist feature│ │ └── gallery/│ │ └── page.tsx│ ├── studio/│ │ └── [[...tool]]/page.tsx # Embedded Sanity Studio│ └── api/│ └── revalidate/│ └── route.ts # Webhook revalidation endpoint├── sanity/│ ├── lib/│ │ ├── client.ts # Sanity client (server + browser)│ │ ├── image.ts # urlFor() image helper│ │ └── queries.ts # All GROQ queries│ ├── schemas/│ │ ├── index.ts # Schema registry│ │ ├── documents/│ │ │ ├── menuItem.ts│ │ │ ├── menuCategory.ts│ │ │ ├── event.ts│ │ │ ├── post.ts│ │ │ ├── artist.ts│ │ │ └── galleryImage.ts│ │ └── objects/│ │ ├── blockContent.ts # Portable Text│ │ ├── seo.ts│ │ └── openingHours.ts│ └── sanity.config.ts # Studio configuration├── components/│ ├── ui/ # Reusable primitives (Button, Badge, etc.)│ ├── layout/ # Nav, Footer, PageWrapper│ ├── sections/ # Full-width page sections│ └── blocks/ # Portable Text custom renderers├── lib/│ └── utils.ts # cn(), formatDate(), formatPrice()├── types/│ └── sanity.d.ts # Generated + manual Sanity types├── public/├── .env.local.example├── next.config.ts├── tailwind.config.ts├── tsconfig.json└── sanity.cli.tsENVIRONMENT VARIABLES
Section titled “ENVIRONMENT VARIABLES”Create .env.local.example with these variables. Never hardcode secrets.
# SanityNEXT_PUBLIC_SANITY_PROJECT_ID=your_project_idNEXT_PUBLIC_SANITY_DATASET=productionNEXT_PUBLIC_SANITY_API_VERSION=2024-01-01SANITY_API_READ_TOKEN=your_read_token # Server-onlySANITY_WEBHOOK_SECRET=your_webhook_secret # Server-only
# SiteNEXT_PUBLIC_SITE_URL=https://yourdomain.com
# Future: Stripe# STRIPE_SECRET_KEY=# NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=SANITY SCHEMA DEFINITIONS
Section titled “SANITY SCHEMA DEFINITIONS”Implement all schemas with full TypeScript types. Follow these specifications exactly.
menuCategory
Section titled “menuCategory”Fields: title (string, required), slug (slug, required), order (number).
menuItem
Section titled “menuItem”Fields: name (string, required), slug (slug, required), category (reference → menuCategory, required), description (text), price (number), priceLabel (string — for ranges like “45–120”), tags (array of strings: e.g. “natural”, “orange”, “vegan”, “new”), available (boolean, default true), image (image with hotspot).
Fields: title (string, required), slug (slug, required), date (datetime, required), endDate (datetime), artist (reference → artist), type (string enum: “live-music” | “tasting” | “dj-night” | “private”), description (Portable Text), ticketUrl (url), ticketPrice (number), capacity (number), image (image with hotspot), seo (seo object), isFeatured (boolean).
Fields: title (string, required), slug (slug, required), publishedAt (datetime), excerpt (text, max 200 chars), body (Portable Text), mainImage (image with hotspot), author (string), tags (array of strings), seo (seo object).
artist
Section titled “artist”Fields: name (string, required), slug (slug, required), bio (Portable Text), photo (image with hotspot), genre (string), links (array of objects with label string and url url), featured (boolean).
galleryImage
Section titled “galleryImage”Fields: title (string), image (image with hotspot, required), alt (string, required), category (string enum: “venue” | “food-wine” | “events” | “artists”), order (number).
seo (object schema)
Section titled “seo (object schema)”Fields: metaTitle (string, max 60 chars), metaDescription (string, max 160 chars), ogImage (image).
blockContent (Portable Text)
Section titled “blockContent (Portable Text)”Enable: all standard marks, h2, h3, h4, blockquote. Custom block types: callout (with tone: info/warning/highlight), imageEmbed (image with caption and alt).
SANITY CLIENT SETUP
Section titled “SANITY CLIENT SETUP”In sanity/lib/client.ts, export two clients:
serverClient— usesSANITY_API_READ_TOKEN,useCdn: false, for RSC data fetching. Never expose to the browser.previewClient— same token,perspective: 'previewDrafts', for Live Preview (implement as a stub if not building preview mode now).
In sanity/lib/queries.ts, define all GROQ queries as named constants. Use defineQuery() from next-sanity for type inference compatibility. Every query must select only the fields it actually uses — never use * projections in production queries.
In sanity/lib/image.ts, export a urlFor() helper using @sanity/image-url.
DATA FETCHING PATTERNS
Section titled “DATA FETCHING PATTERNS”All data fetching happens in React Server Components. Follow this pattern consistently:
// app/(site)/events/page.tsximport { serverClient } from '@/sanity/lib/client'import { EVENTS_QUERY } from '@/sanity/lib/queries'
export const revalidate = 300 // 5 minutes fallback
export async function generateMetadata() { ... }
export default async function EventsPage() { const events = await serverClient.fetch(EVENTS_QUERY) return <EventsList events={events} />}- Use
export const revalidate = Nat the page level to set ISR windows. - For pages that change rarely (gallery, menu categories):
revalidate = 3600. - For pages that change often (events, blog):
revalidate = 300. - The webhook endpoint at
/api/revalidatewill callrevalidatePath()for on-demand revalidation triggered by Sanity publishes. Implement this withSANITY_WEBHOOK_SECRETverification usingisValidSignaturefrom@sanity/webhook.
CACHING STRATEGY
Section titled “CACHING STRATEGY”| Content Type | Strategy | Revalidation |
|---|---|---|
| Menu | ISR revalidate = 3600 | On publish webhook |
| Events | ISR revalidate = 300 | On publish webhook |
| Blog posts | ISR revalidate = 300 | On publish webhook |
| Artist pages | ISR revalidate = 3600 | On publish webhook |
| Gallery | ISR revalidate = 3600 | On publish webhook |
| Homepage | ISR revalidate = 600 | On publish webhook |
The webhook handler at /api/revalidate/route.ts should:
- Verify the
SANITY_WEBHOOK_SECRETsignature. - Accept a body containing
{ _type: string, slug?: { current: string } }. - Map document types to paths and call
revalidatePath()accordingly. - Return
{ revalidated: true, now: Date.now() }.
PAGE SPECIFICATIONS
Section titled “PAGE SPECIFICATIONS”Homepage (/)
Section titled “Homepage (/)”Sections in order: Hero (full-viewport, atmospheric image with headline and CTA buttons), FeaturedEvents (next 3 upcoming events), MenuHighlights (3–4 featured categories or items), FeaturedArtist (latest featured artist), LatestPosts (3 most recent blog posts), GalleryTeaser (6-image masonry grid), OpeningHours + Address block.
Menu (/menu)
Section titled “Menu (/menu)”Fetch all menu categories with their items. Render with a sticky left-side category nav (desktop) / tab-style nav (mobile) that smooth-scrolls to sections. Group items by category. Each item card shows name, description, price, and any tags as badges. Mark unavailable items visually but still show them. Make this page static with long revalidation — menus don’t change daily.
Events (/events and /events/[slug])
Section titled “Events (/events and /events/[slug])”Listing page: separate “Upcoming” and “Past” sections. Each event card shows date, type badge, title, artist name if linked, and ticket/price info. Filter by type (live-music, tasting, etc.) using client-side state (no server round-trip needed for this filter). Detail page: full event info, Portable Text description, artist link if present, ticket CTA button (link to ticketUrl), and related events.
Blog (/blog and /blog/[slug])
Section titled “Blog (/blog and /blog/[slug])”Standard editorial layout. Listing has excerpt, date, tags. Post detail renders Portable Text with custom block renderers for callouts and image embeds. Implement generateStaticParams for all published slugs. Add JSON-LD Article schema to post pages.
Artists (/artists and /artists/[slug])
Section titled “Artists (/artists and /artists/[slug])”Listing: grid of artist cards with photo, name, genre. Detail: full bio (Portable Text), genre, social/streaming links, related past events (queried by artist reference).
Gallery (/gallery)
Section titled “Gallery (/gallery)”Grid layout with lightbox behavior. Filter by category using client-side state. Implement the lightbox as a Client Component island — the surrounding page stays server-rendered. Use next/image with proper sizes attributes throughout.
SEO REQUIREMENTS
Section titled “SEO REQUIREMENTS”Every page must implement Next.js generateMetadata(). Pull metaTitle and metaDescription from the Sanity seo object when present, with sensible fallbacks. Implement Open Graph tags (og:title, og:description, og:image, og:type). Add canonical URLs.
Implement JSON-LD structured data:
- Homepage:
LocalBusinessschema with name, address, opening hours, geo coordinates (stub fields). - Events:
Eventschema with name, startDate, location, offers if ticketed. - Blog posts:
Articleschema.
Create a sitemap.ts using Next.js’s MetadataRoute.Sitemap type that dynamically fetches all published slugs from Sanity.
Create a robots.ts that allows all crawling except /studio.
COMPONENT ARCHITECTURE RULES
Section titled “COMPONENT ARCHITECTURE RULES”Follow these conventions throughout:
- Server by default. Every component is a Server Component unless it needs interactivity (
useState,useEffect, event handlers, browser APIs). Add'use client'only when necessary. - Islands for interactivity. Client Components should be leaf nodes — small, focused interactive pieces (lightbox, mobile menu, filter tabs, contact form) wrapped inside server-rendered parents.
- Props typing. Every component has explicit TypeScript prop types. Use types generated from Sanity schemas where possible.
- No inline styles. Use Tailwind utility classes exclusively. Use
cn()fromlib/utils.ts(wrappingclsx+tailwind-merge) for conditional classes. - Image discipline. Always use
next/image. Always provide meaningfulalttext (from Sanityaltfield, never empty for meaningful images). Always set appropriatesizesattribute. - Loading states. Use Next.js
loading.tsxfiles for route-level Suspense boundaries. Use<Suspense>with skeleton fallbacks for async component boundaries within pages. - Error boundaries. Add
error.tsxfiles at the route group level.
NAVIGATION & LAYOUT
Section titled “NAVIGATION & LAYOUT”The site shell (app/(site)/layout.tsx) wraps all public pages with:
- Header: Logo (left), main nav links (center or right), optional “Book a table” CTA button. Mobile: hamburger → slide-in drawer. Nav links: Home, Menu, Events, Artists, Blog, Gallery.
- Footer: Logo, short tagline, nav links grouped by category, opening hours summary, address, social links (Instagram, Spotify, SoundCloud stubs).
The header should be transparent over hero images on the homepage and solid on inner pages. Implement this with a prop or context, not JavaScript scroll detection — keep it CSS-driven where possible.
EMBEDDED SANITY STUDIO
Section titled “EMBEDDED SANITY STUDIO”Configure the Studio to be accessible at /studio for authorized users only. In production, protect the /studio route with Sanity’s built-in authentication. The Studio should be the full Sanity Studio v3 experience embedded via <NextStudio> from next-sanity/studio.
Organize the Studio desk structure so editors see: Content (Menu, Events, Blog, Artists, Gallery) and Settings as logical top-level groups. Do not expose raw document type lists without grouping.
TYPESCRIPT REQUIREMENTS
Section titled “TYPESCRIPT REQUIREMENTS”strict: truein tsconfig.- Generate Sanity types using
@sanity/typesorsanity-codegen. At minimum, define manual TypeScript interfaces intypes/sanity.d.tsfor all document types. - No
anytypes. Useunknownand narrow appropriately. - Use
zodto validate the webhook payload in/api/revalidate.
FUTURE E-COMMERCE SCAFFOLDING
Section titled “FUTURE E-COMMERCE SCAFFOLDING”Do not implement e-commerce now, but scaffold the following so the future implementation is clean:
- Add
priceandticketUrlfields to theeventschema (already specified above). - Add
pricefield tomenuItem(already specified). - Create a stub
/api/checkout/route.tsthat returns{ error: 'Not yet implemented' }. - Add a
CartProvidercontext stub incomponents/providers/CartProvider.tsxthat exportsuseCart()returning an empty cart — this makes it easy to wire up Stripe/commerce later without restructuring the component tree. - In the event detail page, render a “Buy Tickets” button that links to
ticketUrlfor now, with a comment marking where Stripe checkout will replace this.
PERFORMANCE REQUIREMENTS
Section titled “PERFORMANCE REQUIREMENTS”The site must achieve Lighthouse scores of 90+ across all categories on the homepage and key inner pages. Specific requirements:
- All images use
next/imagewith correctwidth,height, andsizes. Hero images usepriorityprop. - No render-blocking third-party scripts. Any analytics (e.g., Vercel Analytics) must use
next/scriptwithstrategy="afterInteractive"orlazyOnload. - Fonts loaded via
next/font— no external<link>font imports. - Tailwind CSS is purged in production (default behavior — do not disable).
- Total JavaScript shipped to the client should be minimal. Audit Client Components to ensure no large libraries are accidentally bundled client-side.
CODE QUALITY STANDARDS
Section titled “CODE QUALITY STANDARDS”- All components under 150 lines. Extract sub-components when pages grow complex.
- GROQ queries co-located in
sanity/lib/queries.ts, not scattered across pages. - No magic strings — use TypeScript enums or
as constobjects for event types, menu tags, gallery categories, etc. - Consistent file naming:
kebab-casefor files,PascalCasefor component exports. - Every async Server Component should handle the case where Sanity returns
nullor an empty array gracefully — no unhandled crashes. - Add JSDoc comments to non-obvious utility functions and all GROQ queries explaining what they return.
DEPLOYMENT CHECKLIST (Vercel)
Section titled “DEPLOYMENT CHECKLIST (Vercel)”When the project is ready for deployment, ensure:
- All environment variables are set in the Vercel project dashboard.
- The Sanity CORS origins include the production Vercel domain and any preview deployment URLs (
*.vercel.app). - A Sanity webhook is configured pointing to
https://yourdomain.com/api/revalidatewith theSANITY_WEBHOOK_SECRETand the trigger set todocument.publishfor all document types. - Vercel’s image optimization is enabled (default).
- The
/studioroute is excluded from public Vercel previews if desired (usevercel.jsonredirects or Sanity auth).
WHAT NOT TO DO
Section titled “WHAT NOT TO DO”- Do not use the Next.js
pages/directory. Use App Router exclusively. - Do not use
getServerSidePropsorgetStaticProps— these are Pages Router patterns. - Do not fetch data in Client Components using
useEffect+ fetch unless absolutely necessary. Use RSC data fetching. - Do not install
axios— use nativefetch. - Do not use CSS Modules or styled-components — use Tailwind exclusively.
- Do not store secrets in
NEXT_PUBLIC_prefixed variables. - Do not use
anyTypeScript type. - Do not commit
.env.local— only commit.env.local.example. - Do not add unnecessary dependencies. Every package added should have a clear justification.
STARTING INSTRUCTION FOR THE AGENT
Section titled “STARTING INSTRUCTION FOR THE AGENT”Begin by:
- Initializing the Next.js project with
pnpm create next-appusing the App Router, TypeScript, Tailwind CSS, and ESLint. - Installing core dependencies:
next-sanity,@sanity/image-url,@sanity/webhook,clsx,tailwind-merge,zod. - Creating the full directory structure as specified above.
- Implementing all Sanity schemas and the Sanity client configuration.
- Setting up the embedded Studio at
/studio. - Building the GROQ query library and the revalidation webhook endpoint.
- Then building pages in this order: Layout shell → Homepage → Menu → Events → Blog → Artists → Gallery.
Confirm understanding of the full spec before writing any code, and ask for clarification on any ambiguous requirement.