Building a Cannabis Brand Website with Astro and Sanity

Audeos
By
#technology

How we built nw-local.com — Astro 6, Sanity CMS, GitHub Pages, and Claude Code skills for AI-powered content management.

We needed a website for Northwest Local Cannabis — our i502 licensed producer/processor out of Washington State. Not a Wix template with a logo slapped on. Something that could showcase our strains, pull in retail partners, and keep content fresh without a full redeploy every time we drop a new harvest.

Landed on Astro 6, Sanity CMS, GitHub Pages, and Claude Code for AI-powered content management. Free hosting, fast pages, new strains go live in under two minutes.

Here's the breakdown.

Northwest Local Cannabis homepage
Northwest Local Cannabis homepage

Why Astro + Sanity

Astro gives us static HTML. Every page is pre-built, served from a CDN, zero JavaScript unless we explicitly opt in. GitHub Pages hosts it for free.

Content lives in Sanity. Strains, products, blog posts, retailer info — all in the CMS. Astro pulls it at build time with GROQ queries and generates the pages. When we publish something new, a Sanity webhook triggers a GitHub Actions rebuild. Site's live in under two minutes.

Static performance, full CMS flexibility, nothing running at runtime.

The Design

Dark theme with electric green accents. Cannabis culture without being corny — no leaf graphics, no rasta colors. Charcoal background with neon green for anything interactive. The whole theme lives in CSS custom properties in one file.

Headings use Arial Black with a scale transform to match the brand identity from my clothing company The North West Clothing. Glow effects on hover states, scroll-triggered fade-ins, subtle pulse on accent elements. All respecting prefers-reduced-motion. Responsive grid with cards that reflow naturally. System font stack for body text — no layout shift from font loading.

Content Modeling in Sanity

Strains are the core document — name, type, effects, terpenes, THC/CBD ranges, hero image, gallery. Products reference a parent strain with a category and weight. Terpenes are their own documents with aroma profiles and effects.

Everything connects. One strain entry powers its detail page, its card on the listing, its products section, and its OG image. Update it once, everything downstream reflects it on the next build.

Strain Page UX

The strains page is fully client-side — all the data is pre-rendered at build time, filtering and sorting happen in JavaScript with no server round-trips. Search runs across name, type, effects, and terpenes with a 200ms debounce. Filter by indica, sativa, or hybrid. Filter by terpene. Sort by name. Pagination at 12 per page.

Each strain detail page has a hero image, description, effects and terpene badges, and a gallery with a full-screen lightbox. Terpene badges link out to their own resource pages. Products linked to that strain show up below. OG images are generated per strain for social sharing.

The Image Pipeline

Product photography adds up fast. Every strain has a hero image and a gallery, and we're constantly adding new ones. We built a prep-images script to handle the whole workflow.

It takes a folder of photos, converts HEIC/PNG/WebP to JPG, and renames them with SEO-friendly slugs — IMG_3559.HEIC becomes gastro-pop-bud-closeup.jpg. Then it deduplicates at two levels. First, SHA-1 hashing against a local _processed directory to catch files we've already converted. Then it hits the Sanity API to check if that hash already exists in the media library. No duplicate uploads, no bloated CDN.

Run it through a Make target, get a summary of what's new and what's already uploaded, then push the new ones to Sanity with alt text and metadata attached.

Age Gate

Washington State requires age verification for cannabis sites. Ours is a full-screen overlay — dark background, confirm you're 21 or older, stored in localStorage so it only shows once. Click the deny link and you get redirected out. Simple, compliant, doesn't get in the way after the first visit.

Deploy Pipeline

Push to main and GitHub Actions builds the site with Astro and deploys to GitHub Pages. But we're not pushing to main every time we add a strain or publish a blog post — that's what the Sanity webhook handles. Any content publish in Sanity fires a repository_dispatch event to GitHub Actions, triggering a full rebuild. Content goes from CMS to live site in under two minutes with no manual steps.

AI Content Management

This is where it gets interesting. We built Claude Code skills that turn strain creation from a manual process into a conversation.

The /new-strain skill kicks off a research phase — it pulls info from multiple sources, verifies lineage and breeder data, extracts effects, terpenes, and THC ranges. Then it synthesizes everything into an original description. No copy-pasting from Leafly.

For images, Claude analyzes the gallery photos and generates descriptive filenames — bud-closeup, trichome-detail, dense-frosty-nug. Triggers the prep-images pipeline, uploads to Sanity with alt text. If a strain introduces a terpene we don't have yet, it auto-creates the terpene document with aroma, effects, natural sources, and even generates a hero image for it.

The whole thing — research, writing, image processing, document creation — runs through a single conversation. What used to take 30 minutes of jumping between tabs and terminals is now a 5-minute chat.

Wrap-up

The site is live at nw-local.com. Astro and Sanity turned out to be the right call — fast pages, flexible content, zero hosting costs. The image pipeline keeps our media library clean as the strain catalog grows. And the Claude Code skills mean we spend less time on content entry and more time on what actually matters.