cd /news/artificial-intelligence/i-built-an-ai-website-builder-and-he… Β· home β€Ί topics β€Ί artificial-intelligence β€Ί article
[ARTICLE Β· art-11470] src=dev.to β†— pub= topic=artificial-intelligence verified=true sentiment=↑ positive

# I Built an AI Website Builder and Here's What Actually Happened

The article describes the development of CrafticWeb, an AI-powered website builder that generates real HTML/CSS/JS code from user descriptions. The builder uses DeepSeek via OpenRouter for AI generation, features a React frontend with Node.js/Express backend, and includes Google login, a credits system, and Stripe billing. Key technical challenges included handling AI responses wrapped in markdown code fences, fixing live preview issues by switching from srcdoc to Blob URLs, and resolving cross-origin cookie authentication problems in production.

read5 min views22 publishedMay 23, 2026

I've been wanting to build something with AI for a while now. Not just a wrapper around ChatGPT, but something that actually feels useful. So I built CrafticWeb β€” you describe a website, and it generates real HTML/CSS/JS for you. You can tweak it with follow-up prompts, edit the code directly, preview it live, and deploy it with one click.

It's live, it works, and I learned a ton building it. Here's the honest story.

The Idea #

The core concept is simple. You type something like "a landing page for a coffee shop with a dark theme" and the app sends that to an AI model (DeepSeek via OpenRouter), gets back a full HTML document, and renders it in the browser instantly. You can keep chatting to refine it, open it in a Monaco editor to manually tweak things, and when you're happy β€” deploy it to a public URL.

I also added Google login, a credits system, and Stripe billing because I wanted to build something that felt like a real product, not just a weekend demo.

The Stack #

For the frontend I went with React + Vite, Redux for state, Tailwind for styling, Framer Motion for animations, and Monaco Editor for the in-browser code editor. The backend is Node.js + Express, MongoDB for storage, JWT with httpOnly cookies for auth, and OpenRouter to talk to the AI. Stripe handles billing.

Nothing too exotic. The interesting stuff is in how it all fits together.

What Actually Broke (And How I Fixed It) #

Getting the AI to return clean JSON

This was my first real headache. The backend sends a master prompt to DeepSeek via OpenRouter and expects back a clean JSON object like this:

{
  "message": "Your website is ready",
  "code": "<full HTML document>"
}

Simple enough in theory. In practice, the model kept wrapping its response in markdown code fences β€” you know, the triple backtick blocks β€” which meant JSON.parse()

would just throw and the whole thing would fail. My fix was a small utility function that strips out those fences before parsing, and if the JSON is still broken, the backend retries the API call up to three times. Once I had that in place, generation became reliable.

Rendering the preview without CORS exploding

My first attempt at the live preview used srcdoc

to inject HTML into an iframe. Vite did not like that. It kept injecting its own scripts into the content and everything would break with CORS errors.

The fix was switching to Blob URLs. Instead of passing the HTML as a string attribute, you create a Blob from it, generate a local object URL, and point the iframe's src

at that:

const blob = new Blob([html], { type: "text/html" });
const url = URL.createObjectURL(blob);
iframeRef.current.src = url;

The browser treats it like a local file, completely sandboxed, no CORS, no Vite interference. One small change, completely solved it.

Authentication Flow

Google Auth looks simple from the outside but there are a few moving pieces. Here's the full flow:

  • User clicks "Continue with Google"
  • Firebase signInWithPopup

returns the user's info - Frontend POSTs { name, email, avatar }

to/api/v1/auth/register

  • Backend creates or finds the user in MongoDB, generates a JWT, and sets it as an httpOnly

cookie - All subsequent requests send the cookie automatically via withCredentials: true

On the frontend every Axios call goes out with credentials attached:

axios.defaults.withCredentials = true;

And on the backend the cookie is set like this:

res.cookie("token", jwt, {
  httpOnly: true,
  secure: true,
  sameSite: "none",
});

The httpOnly

flag means JavaScript on the client can never read the token directly β€” much safer than storing it in localStorage

. And sameSite: 'none'

  • secure: true

is what makes the cookie actually travel cross-origin between your deployed frontend and backend.

Cookies not working after deployment

This one burned me for an embarrassingly long time. I deployed to Render, everything looked fine locally, and then authenticated requests just stopped working in production. The cookie was being set, but the browser wasn't sending it back with requests.

Turned out I needed two things together: sameSite: 'none'

and secure: true

on the cookie, and NODE_ENV=production

set in Render's environment variables. Without that last one, the backend was still behaving like it was in development mode and the production cookie settings never kicked in. Once both were set, it worked perfectly.

Google OAuth popup silently failing

Firebase's signInWithPopup

was failing with auth/popup-closed-by-user

even when the user hadn't closed anything. After digging around I found the culprit β€” a Cross-Origin-Opener-Policy

header I'd added to the backend. It was preventing the OAuth popup from communicating back to the parent window. Removing that header fixed it completely.

The Thing I'm Most Proud Of #

Honestly? The credits and billing system. It felt intimidating before I built it, but it's pretty clean now. Stripe Checkout handles the payment UI, Stripe Webhooks confirm the payment server-side and add credits to the user's account in MongoDB, and credits get deducted on each successful generation. The one thing I'd warn anyone about β€” use express.raw()

as the body parser for your webhook route, not express.json()

. The second one breaks Stripe's signature verification and you'll spend an hour wondering why webhooks aren't firing.

What's Next #

I want to add streaming so the HTML appears progressively instead of all at once after a delay β€” that would make it feel way snappier. Version history is on the list too, so you can go back to an earlier generation if you over-prompted yourself into a mess.

If you want to check it out or dig into the code, it's all open source.

πŸ‘‰ GitHub: github.com/KunalChakraborty445/Crafticweb

🌐 Live Demo: crafticwebai-beta.onrender.com

Built with too much console.log

and not enough sleep β€” Kunal

── more in #artificial-intelligence 4 stories Β· sorted by recency
── more on @crafticweb 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/i-built-an-ai-websit…] indexed:0 read:5min 2026-05-23 Β· β€”