How I set up a studio site and product subdomain on Vercel and Cloudflare in a weekend
A practical playbook for shipping a studio site with a product subdomain using Next.js, Vercel, Cloudflare, and Claude. Real timelines, real prompts, no fluff.
I had a constraint: HIBUD's product page had to be live before I could submit Apple Developer enrollment. Apple reviews the public site as part of organisation verification. No site, no app submission. So I gave myself a weekend, opened a fresh Next.js repo, and shipped a studio site at ctrlsze.studio and a product page at hibud.ctrlsze.studio. One codebase. Two visual systems. One person. This is what worked.
Most "weekend project" posts hand-wave the parts that actually take time. This one doesn't. Real timestamps, real config, the prompts I used, the bug that ate forty minutes on Sunday. If you're building a studio and a first product on a deadline, this is the sequence I'd follow if I were doing it again.
The architecture, and why subdomains
I considered three shapes. Separate Next.js apps per site, a single app with multiple Vercel deployments, and a single app routing by host header. The third one won.
One codebase with host-based routing means I share the components that should be shared (footer copy, contact form plumbing, the unsubscribe handler) and keep the things that shouldn't (visual system, hero treatment, type pairing) cleanly separate. One deploy pipeline. One environment configuration. One set of secrets to rotate. The tradeoff is a small amount of routing logic in middleware — and that logic is fifteen lines.
Subdomains over subdirectories was the other call. Search engines treat subdomains as separate sites and subdirectories as part of the parent. A subdirectory inherits authority faster — short-term win — but a subdomain develops its own ranking signal and brand. HIBUD is a product with its own identity, not a CTRLSZE feature. It earned its own subdomain.
The workflow that made a weekend possible
Before any code, the meta-process. I used three Claude surfaces, each doing one thing well, and one workflow trick that 5x'd the quality of everything that came out.
The first surface was a planning chaton claude.ai. Architectural decisions, scope, sequencing. The conversation that produced the build plan I'm now executing. This chat ran long; I treated it as a working document and periodically asked Claude to summarise the locked decisions so I could paste them into the second surface.
The second surface was Claude Design. I mocked every page visually before writing any code. CTRLSZE first — three reference sites pasted in (Vercel, Linear, Anthropic.com), the vibe described in plain English, then iteration on hero treatment, type pairing, accent colour, motion. Locked the design tokens. Then a completely separate session for HIBUD — different references (Are.na, Pitchfork, journaling zines), different system entirely. Warm cream palette, serif body, magazine layout. The point of designing before coding is that every component decision in the build chat afterwards was constrained by the locked design system. Claude Code didn't have to make taste decisions. It just implemented to spec.
The third surface was a series of per-block build chats, one per implementation unit. DNS setup. Wildcard middleware. Posts schema. The unsubscribe flow. Each one started with a tight, reusable prompt rather than vague "help me build X".
Saturday morning: domains and DNS
I bought ctrlsze.studio through Cloudflare Registrar. Cloudflare sells domains at cost, which means renewals are $20–25/year for a .studioinstead of the $40–50 most registrars charge. I turned on DNSSEC and registrar lock immediately. Both are free, both meaningfully reduce attack surface, and both are the kind of thing you forget to enable later if you don't do it now.
Then the wildcard DNS record. The whole point of the architecture is *.ctrlsze.studio resolves to Vercel, and the app decides what to do based on the host header.
Type Name Content Proxy
A @ 76.76.21.21 DNS only
CNAME www cname.vercel-dns.com DNS only
CNAME * cname.vercel-dns.com DNS onlyProxy off, not on.Vercel handles SSL termination itself; if you proxy through Cloudflare without configuring origin certificates, you'll get a redirect loop or a 526 error. I learned this the slow way. Real elapsed time for this step including DNS propagation: ~30 minutes.
Saturday afternoon: Vercel and the Next.js skeleton
create-next-appwith TypeScript and the App Router. Pushed to GitHub. Connected to Vercel. The first deploy worked immediately, which is the boring miracle of this stack — no config files, no build pipeline to debug, no "works on my machine."
Then the part that catches most people. In Vercel, you add domains on the project — you add ctrlsze.studio as the apex, and you add *.ctrlsze.studioseparately as the wildcard. Vercel auto-issues SSL for both, but the wildcard takes a few minutes longer than the apex. If you check too early you'll see a yellow indicator and panic. Wait five minutes, refresh, you're good.
The middleware that does the host-based routing is the most copy-pasteable code in this post. Treat this as the canonical version:
// proxy.ts (or middleware.ts in older Next.js)
import { NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
const hostname = request.headers.get("host") ?? "";
const url = request.nextUrl.clone();
const cleanHost = hostname.split(":")[0];
const pathname = url.pathname;
const isHibud =
cleanHost === "hibud.ctrlsze.studio" ||
cleanHost.startsWith("hibud.localhost") ||
cleanHost.startsWith("hibud.lvh.me") ||
cleanHost === "hibud.test";
if (isHibud) {
if (pathname.startsWith("/hibud/")) {
return NextResponse.next();
}
const newPath = `/sites/hibud${pathname === "/" ? "" : pathname}`;
url.pathname = newPath;
return NextResponse.rewrite(url);
}
return NextResponse.next();
}
export const config = {
matcher: [
"/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|llms.txt).*)",
],
};Two things worth flagging. The matcher excludes static assets, API routes, and SEO files — these should never be rewritten or they'll break in subtle ways. And the lvh.me and localhost handling lets me develop subdomain routing locally without editing /etc/hosts: hibud.lvh.me:3000 resolves to 127.0.0.1 and Just Works.
Saturday evening: two design systems, deliberately
I built CTRLSZE and HIBUD as two completely separate visual systems and that decision shaped everything else.
CTRLSZE is tech-studio: Geist Sans, Fraunces serif for accents, near-paper background (#fafaf7), single accent (#0047ab royal blue), restrained motion. The family I was looking at: Vercel, Linear, Stripe, Anthropic.com. Built in Tailwind v4 with custom design tokens in @theme.
HIBUD is artsy-cottagecore-journaling: warm cream, earth tones, serif body, handwritten accents, magazine layout, gentle motion. Family: Are.na, Pitchfork, journaling zines. Different fonts, different palette, different rhythm.
These two systems share no DNA. That's the point. A studio front door and a product home don't need to feel like the same thing — they need to feel like the right thing for what they are. Approximately 2x the design and component work versus a single-design site, but it's the deliberate choice. Building both in Claude Design first meant I had every spacing, type scale, and colour locked before I wrote a single component.
Sunday: content, SEO, and the things I almost forgot
Sunday was the long tail. The thirty things you don't notice if they're there and you definitely notice if they're not. I'll list them here because the list is the point:
Sitemap and robots. Both are Next.js conventions — files at app/sitemap.ts and app/robots.ts. The sitemap pulls page entries from the app router and posts from the posts registry, so it stays in sync without manual updates.
llms.txt. Emerging standard for AI crawlers, like robots.txt but oriented around what an LLM should read first. Recent posts plus key URLs in a flat, scannable format. Zero downside; possible meaningful upside as AI assistants increase their share of search.
OG images.Per-post, auto-generated using Next.js's ImageResponse API. Title, excerpt, wordmark, on-brand. No hand-designed PNGs to ship per post.
JSON-LD. Article schema on every post. FAQPage when the post has FAQ items. Speakableon body content for voice/AI read-aloud. Each one is a small JSON object, validated by Google's Rich Results Test in 30 seconds.
Canonical URLs. HIBUD is reachable at both hibud.ctrlsze.studio and ctrlsze.studio/products/hibudvia the redirect rules. The subdomain is canonical. Set it explicitly in metadata so crawlers don't hesitate.
The 404. On-brand, helpful, links to home/blog/projects, custom search input. Default Next.js 404 is utilitarian; the custom one is signature.
Plausible. One script tag in the layout. No cookies, no banner needed.
Legal pages. Privacy, terms, cookies, refunds. Templated to start; refined later. Apple requires them, the AU Privacy Act requires them, Stripe requires them.
The bug that ate forty minutes.An unsubscribe page wouldn't build. Turbopack errored on a generic type annotation. The cause was that I'd copied code through a rich-text surface (Apple Notes, in this case) which had silently rewritten signups.id as a markdown auto-link: [signups.id](http://signups.id). Across four spots in one file. The fix was a one-line sed; the lesson is to always paste code as plain text (Cmd+Shift+V in VS Code) when the clipboard might have passed through a rich-text app.
What it cost
Domain: ~$22 AUD per year. ASIC business name registration for CTRLSZE under AURACO PTY LTD: $42 for one year, $98 for three. Vercel: free tier (Hobby). Neon Postgres: free tier. Resend: free tier (up to 3,000 emails/month). Cloudflare: free. Plausible: free during trial, $9/mo afterwards. Total ongoing cost through the first hundred subscribers: under $10/month plus the domain.
The expensive part isn't the infrastructure. It's the time. The infrastructure exists for free precisely because so many people's time has gone into making it free.
What I'd do differently
Three things, in order of pain caused.
Set up the prompt-refinement workflow on day one. I stumbled into it on Saturday afternoon, after a few hours of mediocre AI output that I had to revise heavily. Once I started having one chat write the prompt for the next chat, the quality of the second chat's output stepped up sharply. If I were doing this again I'd formalise that pattern from the first prompt.
Lock design tokens before any page work. I did this for CTRLSZE but skipped ahead with HIBUD and ended up with small inconsistencies — two slightly different cream values, two slightly different paragraph rhythms — that I had to track down and fix. Always design the system first, even if the system feels too small to need a system.
Underestimate wildcard SSL provisioning by less. It took ten minutes, not the two I expected. Not a real problem; just an annoyance if you're building against a deadline and watching the dashboard tick.
The playbook
Compressed sequence, no context, in case you're here for the TL;DR:
- Buy the domain on Cloudflare Registrar. DNSSEC + registrar lock immediately.
- Add wildcard DNS: A apex to Vercel, CNAME
*tocname.vercel-dns.com, both DNS-only (proxy off). create-next-appwith TypeScript + App Router. Push to GitHub. Connect to Vercel.- In Vercel, add the apex domain and the wildcard separately. Wait for SSL (apex is fast; wildcard takes a few minutes more).
- Write 15-line middleware: read host header, rewrite to product routes for matching subdomains, fall through for the studio.
- Build both visual systems in Claude Design before any pages. Lock tokens. Export to design notes.
- Build pages with Claude Code, prompted from briefs that came out of a separate refinement chat (one chat writes the prompt; the next chat builds).
- Add sitemap, robots, llms.txt, JSON-LD per post, OG images, canonical URLs, legal pages, 404. Plausible last.
- Paste code as plain text. Always.
Frequently asked
- Why .studio instead of .com?
- ctrlsze.com was unavailable at a price I'd pay, and .studio communicates what CTRLSZE is in a way .com doesn't. The TLD costs ~$22/yr instead of $15, and SEO ranking signals don't materially differentiate by TLD any more — Google has said this repeatedly. The only practical downside is some users still type .com out of habit, so I bought ctrlsze.com later as a redirect once it was available.
- Can I do this with Cloudflare Pages instead of Vercel?
- Yes, with caveats. Cloudflare Pages supports Next.js but not every feature works at the edge — ImageResponse for OG generation needs the Node runtime, which Pages handles via Functions but with extra config. Vercel is the path of least friction for Next.js 15. If you're already deep in the Cloudflare ecosystem and want one bill, Pages is reasonable; otherwise, Vercel is the default.
- Do I need a separate Next.js app per product?
- No. One Next.js app with host-based routing handles N products as long as each product fits in the same dependency footprint. Separate apps make sense when one product needs a radically different stack — say, a Python backend for ML inference — or when the apps will be sold or open-sourced separately. For a studio shipping multiple small products, one repo is simpler.
- How does SEO work with subdomains vs subdirectories?
- Search engines treat subdomains as separate sites and subdirectories as part of the parent. That means a subdirectory inherits the parent's authority faster, but a subdomain can develop its own audience and ranking signal. For products with their own brand identity (HIBUD as a product, not a CTRLSZE feature), a subdomain is the right call. Set canonical URLs carefully so the same content at /products/hibud and hibud.ctrlsze.studio doesn't confuse crawlers.
- What if I want the product on its own domain later?
- A subdomain is portable. When the product earns its own domain, you set up the new domain in Vercel, copy or rewrite the product routes to live there, and put a 301 redirect from the subdomain to the new domain. Search authority transfers (slowly) via the redirect chain. Starting on a subdomain costs you nothing if you eventually graduate.
Notes from the build will keep landing here. The next post is on the two-design-system trade-off in detail — when it's worth 2x the component work and when it isn't.
Sze. (2026, May 3). How I set up a studio site and product subdomain on Vercel and Cloudflare in a weekend. CTRLSZE. https://ctrlsze.studio/blog/studio-subdomain-weekend
https://ctrlsze.studio/blog/studio-subdomain-weekend
›BibTeX
@misc{ctrlsze-studio-subdomain-weekend-2026,
author = {Sze},
title = {How I set up a studio site and product subdomain on Vercel and Cloudflare in a weekend},
year = {2026},
month = {May},
url = {https://ctrlsze.studio/blog/studio-subdomain-weekend},
note = {CTRLSZE}
}