82 commits. 29,183 lines of code. Three languages. WCAG AA accessible. About 10 hours of actual work, shipped the next morning. One founder, one AI agent, zero budget.
This isn’t a toy demo. This is hayka-pacha.io, the site you’re reading right now.
Here’s exactly how we did it, what worked, what broke, and whether this workflow is repeatable for your next project.
Why build a landing page in one day?
We needed a web presence fast. A real marketing site, not a placeholder. Something that makes potential partners think “these people know what they’re doing.” Here’s what we needed:
- Trilingual (English, French, Spanish) with proper hreflang and i18n routing
- Fast: sub-2s load times on every device
- Accessible: WCAG AA compliant from day one
- SEO-ready: structured data, sitemaps, meta tags, Open Graph images
- Blog-ready: content collections with MDX, draft system, scheduled publishing
- Lead capture: contact form with categorized inquiries, branded email notifications
A typical agency would quote 2-4 weeks and $5,000-$15,000 for this scope. We spent $0 and 17 hours.
The stack: why every choice matters
We didn’t pick tools because they’re trendy. Every dependency is there because it solves one problem without creating two more.
| Layer | Tool | Why this one |
|---|---|---|
| Framework | Astro 6 | Static-first, native i18n routing, zero JS by default |
| Styling | Tailwind CSS v4 + DaisyUI v5 | Utility-first + component library = fast UI without design debt |
| Content | MDX + Content Collections | Type-safe blog with Zod schema validation |
| Hosting | Cloudflare Workers | Edge deployment, global CDN, free tier handles our scale |
| Forms | Resend | Transactional email without SDK bloat: one API call |
| Testing | Playwright | E2E tests for i18n routing and form submission |
Total dependencies: 12. No state management. No CSS-in-JS. No component library dragging 47 peer dependencies behind it. Fewer moving parts means less breaks.
What is Astro? Astro is a web framework that renders pages to static HTML at build time, shipping zero JavaScript to the browser unless you explicitly opt in. It’s ideal for content-heavy sites where performance and SEO matter more than client-side interactivity.
The AI-assisted workflow: what actually happened
Let’s be specific about what “AI-assisted” means. We didn’t type “build me a website” and walk away. A human made every decision. The AI wrote the code, fast.
Phase 1: Architecture (~1 hour)
I wrote a 400-line design spec. Brand identity, colors, typography, section requirements. Everything from the Quechua etymology of our name to exact OKLCH color values. If it wasn’t in the spec, it wasn’t getting built.
Claude Code took that spec and produced a 12-task implementation plan with dependencies and file paths. Then it scaffolded the Astro project, wired i18n, and created the content collection schema.
Key decision: We chose Astro’s native i18n routing (/{lang}/ prefix on all routes) over third-party i18n libraries. This meant the router handles language detection at the edge, and translation files stay as simple JSON with no runtime overhead.
// src/content.config.ts: the schema every blog post must satisfy
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
lang: z.enum(['en', 'fr', 'es']),
category: z.enum(['SEO', 'Infra', 'Automation', 'Business']),
readTime: z.number(),
draft: z.boolean().default(false),
}),
});
This schema is enforced at build time. If a blog post is missing a required field or has an invalid category, the build fails. No silent errors, no broken pages in production.
Phase 2: Components (~5.5 hours)
This phase was fast. I described what I wanted in plain English, the AI wrote the component, I reviewed it, pushed back on what looked wrong, and the AI fixed it. Rinse, repeat, eight times.
We built 8 components in 5.5 hours:
| Component | Purpose | JS Required? |
|---|---|---|
Nav.astro | Logo + language switcher | No |
Hero.astro | Full-screen hero with CTAs | No |
Manifesto.astro | 3-beat brand narrative with scroll animations | Minimal (IntersectionObserver) |
TechStrip.astro | Infinite logo marquee, 13 tech partners | No (CSS-only @keyframes) |
BlogPreview.astro | Latest 3 articles with category badges | No |
Contact.astro | Lead capture form with dropdown | Minimal (fetch API) |
Footer.astro | Links, social, language switcher | No |
LanguageSwitcher.astro | EN/FR/ES toggle | No |
6 out of 8 components ship zero JavaScript. The only JS on the page is a lightweight IntersectionObserver for scroll animations (under 1KB) and the form submission handler. Everything else is rendered to static HTML at build time.
Phase 3: Polish and hardening (~6 hours)
Most “build it fast” projects skip this part. We didn’t.
Design system: Every color, spacing value, and typography rule got extracted into .claude/skills/hayka-design-system/SKILL.md. Now when the AI builds a new component, it reads this file first. Brand consistency without thinking about it.
Accessibility audit: We ran AccessLint across every component and fixed:
- Missing form labels
- Insufficient color contrast (recalibrated the entire OKLCH palette for WCAG AA)
- Missing skip navigation link
- Focus styles for keyboard navigation
Security hardening:
- CSRF origin checks on the contact form API
- Security headers via Cloudflare (CSP, X-Frame-Options, HSTS)
- Environment secrets read from Cloudflare Workers runtime, not baked into the build
i18n completeness: Every single string in every component was extracted to translation files. We caught 14 hardcoded English strings that would have shipped untranslated.
Phase 4: Deploy and iterate (~4 hours)
Cloudflare Workers deploy, OG image, robots.txt, sitemap. Then 23 commits of pure copy tweaks. Rewording the manifesto. Fixing anglicisms in the French version. Getting the guillemets right.
Last commit landed before lunch.
What broke, and what we learned
Stuff broke. Here’s what.
The CI cross-platform nightmare
Our biggest time sink: 5 commits fighting native binary dependencies. Tailwind CSS v4 uses platform-specific native binaries (@tailwindcss/oxide). The package-lock.json generated on macOS didn’t include the Linux binaries needed for CI. We tried npm ci, cache clearing, and explicit optional dependencies before finding a stable solution.
Lesson: When using tools with platform-specific native modules, test your CI pipeline early, not after you’ve built 8 components.
The Cloudflare Pages vs Workers confusion
We initially configured for Cloudflare Pages, but the Astro Cloudflare adapter outputs a Worker format. Two commits were wasted on the wrangler.jsonc configuration conflict between Pages and Workers deployment modes.
Lesson: Read the adapter documentation before the first deploy, not after wrangler deploy fails.
The invisible content bug
We added a CSS class that hid content until JavaScript loaded (for scroll-in animations). This meant crawlers and screenshot tools saw empty sections. One commit to fix, but it could have killed our SEO if we’d missed it.
Lesson: Never hide content behind JavaScript unless you have a fallback. Search engines don’t wait for your IntersectionObserver.
The numbers
| Metric | Value |
|---|---|
| Actual work time | ~10 hours |
| Commits | 82 |
| Files created/modified | 222 |
| Lines of code | 29,183 |
| Dependencies | 12 |
| Languages | 3 (EN/FR/ES) |
| Components | 8 |
| JavaScript shipped to browser | < 2KB |
| Translation keys per language | 50 |
| Bug fix commits | 26 (32% of total) |
32% of commits were bug fixes. That’s normal. Moving fast means breaking things. The difference is we caught them in seconds because the AI could fix-and-test in a loop that would take a human ten times longer.
Is this workflow repeatable?
Yes. But it depends on you, not on the AI.
The 400-line design spec made this work. Without it, the AI would’ve guessed at every design decision and gotten most of them wrong. Garbage spec, garbage output. That’s true regardless of the model.
The disciplined stack helped too. 12 dependencies. Each one justified. The AI doesn’t struggle with your code. It struggles with your 47 transitive dependencies and the weird interaction between three overlapping libraries.
And honestly? I’ve been using Astro, Tailwind, and Cloudflare for years. The AI made me faster. It didn’t make me capable. If I didn’t already know this stack, I’d have spent the 17 hours debugging the AI’s mistakes instead of building.
This was also a content site. Static pages, a form, some animations. If you’re building something with complex state management or real-time features, expect more back-and-forth with the AI and more manual fixes.
What’s next
This site is the base for everything we’ll publish. Case studies about our infrastructure. Deep dives into the systems we run across 400+ sites on Kubernetes.
Next up: how our AI agents manage that entire fleet without us touching anything. Check our blog for the full series, or get in touch if you want us to build something like this for you.
FAQ
How much does it cost to build a site this way?
$0 in direct costs. Astro is open-source. Cloudflare Workers has a free tier. Claude Code is a subscription we already pay for. The real cost? 17 hours of a founder's time and a decade of experience that made those hours count.
Can a non-technical person use this workflow?
No. You need to understand web architecture, deployment, and how to write a clear spec. The AI writes code. You make decisions. That split won't change anytime soon.
Why Astro instead of Next.js?
For a marketing site that's 95% static content, shipping a React runtime makes no sense. Astro sends zero JS by default. Better Core Web Vitals without even trying. Next.js is great when you actually need client-side interactivity.
Did you use any other AI tools besides Claude Code?
Claude Code for everything. AccessLint for accessibility checks (via MCP). A design generation tool for visual components. No Figma. The design system lives in code.
How do you handle translations?
One JSON file per language (src/i18n/en.json, fr.json, es.json), 50 keys each. Blog posts are separate MDX files per language, written by hand. No machine translation. Astro handles the routing natively: /en/blog/, /fr/blog/, /es/blog/.