[{"data":1,"prerenderedAt":657},["ShallowReactive",2],{"blog-posts":3},[4,369],{"id":5,"title":6,"author":7,"authorImage":8,"authorTitle":9,"body":10,"category":358,"date":359,"description":360,"extension":361,"image":8,"meta":362,"navigation":363,"path":364,"readTime":365,"seo":366,"stem":367,"__hash__":368},"content/blog/headless-bi-rent-the-engine.md","Headless BI: Rent the Engine, Own the Frontend","Vibedasher Team",null,"Product Team",{"type":11,"value":12,"toc":350},"minimark",[13,17,25,41,47,52,60,63,118,130,133,137,144,158,177,181,184,187,194,198,201,289,300,304,322,325,329,346],[14,15,6],"h1",{"id":16},"headless-bi-rent-the-engine-own-the-frontend",[18,19,20,21],"p",{},"There's a pattern that keeps winning in developer tools: ",[22,23,24],"strong",{},"split the thing you should own from the thing you should rent.",[26,27,28,35],"ul",{},[29,30,31,34],"li",{},[22,32,33],{},"shadcn/ui"," gave you the component code. You own it, you edit it, it lives in your repo. No runtime dependency on a component vendor.",[29,36,37,40],{},[22,38,39],{},"Supabase"," rented you the hard backend — Postgres, auth, storage, realtime — behind a clean SDK. You don't run the database; you call it.",[18,42,43,44],{},"Headless BI is that same split, applied to analytics: ",[22,45,46],{},"own your dashboard code, rent the data engine.",[48,49,51],"h2",{"id":50},"what-headless-means-here","What \"headless\" means here",[18,53,54,55,59],{},"Classic embedded analytics gives you an iframe. The vendor hosts the chart, the vendor owns the rendering, and you get a rectangle on your page. It's fast to start and genuinely useful — but the dashboard is never ",[56,57,58],"em",{},"yours",". You can't restyle it past the theming knobs they expose, you can't fold it into your component library, and you can't diff it in a pull request.",[18,61,62],{},"Headless BI separates the two layers that were welded together:",[64,65,66,82],"table",{},[67,68,69],"thead",{},[70,71,72,76,79],"tr",{},[73,74,75],"th",{},"Layer",[73,77,78],{},"Who owns it",[73,80,81],{},"Why",[83,84,85,102],"tbody",{},[70,86,87,94,99],{},[88,89,90,93],"td",{},[22,91,92],{},"Frontend"," — charts, layout, filters, interactions",[88,95,96],{},[22,97,98],{},"You",[88,100,101],{},"It's your product surface. It should be your code, your design system, your repo.",[70,103,104,110,115],{},[88,105,106,109],{},[22,107,108],{},"Engine"," — connectors, ETL, transforms, query, freshness, caching, row-level filtering",[88,111,112],{},[22,113,114],{},"Us",[88,116,117],{},"It's the hard, boring, expensive part. Renting it is the whole point.",[18,119,120,121,124,125,129],{},"You describe a dashboard, our AI builds it, and then you ",[22,122,123],{},"eject"," it: your AI pulls the dashboard's source via the Vibedasher MCP and rebuilds it natively in your app. The charts and layout become ordinary files in your repo. The only thing still wired to us is one call — ",[126,127,128],"code",{},"client.query({ sql, params })"," — that returns typed rows from the engine.",[18,131,132],{},"That's the shadcn move (you own the code) plus the Supabase move (you rent the hard backend), in one product.",[48,134,136],{"id":135},"why-owning-the-code-beats-owning-a-rectangle","Why owning the code beats owning a rectangle",[18,138,139,140,143],{},"The instinct is that an iframe is ",[56,141,142],{},"less"," lock-in than code threaded through your app — you can always delete a rectangle. That's exactly backwards for the thing you care about.",[26,145,146,152],{},[29,147,148,151],{},[22,149,150],{},"An iframe is a black box you rent forever."," You never own the dashboard; you rent a view of it. Restyling, testing, and composing it are all limited to what the vendor's embed API allows.",[29,153,154,157],{},[22,155,156],{},"Ejected code is yours."," It reviews in a PR, restyles with your tokens, tests in your suite, and composes with the rest of your UI. If you outgrow a chart, you edit it — you're not filing a feature request.",[18,159,160,161,168,169,172,173,176],{},"The interesting inversion: ",[22,162,163,164,167],{},"owning the frontend code makes the engine ",[56,165,166],{},"stickier",", not weaker."," When your dashboards are real components in your repo, built around one SDK call, that call is threaded through your product. It's far harder to rip out than an iframe you can delete in one line — because you've built ",[56,170,171],{},"on"," it, not just ",[56,174,175],{},"embedded"," it. That's the shadcn effect. Lock-in that comes from usefulness, not from a hostage rectangle.",[48,178,180],{"id":179},"the-part-worth-renting","The part worth renting",[18,182,183],{},"Here's the honest pitch for renting the engine: the frontend is the fun part, and the engine is the part that quietly eats your quarter.",[18,185,186],{},"A production data layer is connectors that survive schema drift, incremental ETL that doesn't reprocess everything nightly, a query engine that joins across managed datasets, freshness tracking, a caching layer so repeat queries are instant, and row-level filtering so tenant A never sees tenant B's data. None of that is a weekend project, and none of it is your differentiator. It's undifferentiated heavy lifting — the textbook definition of what to rent.",[18,188,189,190,193],{},"So we run it, and we meter ",[22,191,192],{},"engine usage"," — queries and ETL — pay-as-you-go. You're not paying per \"dashboard view\" or per seat for a rectangle; you're paying for the compute the engine actually does. Cache hits are cheap. The frontend is free — it's your code.",[48,195,197],{"id":196},"the-boundary-is-one-method","The boundary is one method",[18,199,200],{},"The entire contract between your app and the engine is a single call:",[202,203,208],"pre",{"className":204,"code":205,"language":206,"meta":207,"style":207},"language-ts shiki shiki-themes github-light github-dark","const { columns, rows } = await client.query({\n  sql,      // alias-only SQL (never a physical table name)\n  params,   // { column: value } filters, bound server-side\n});\n// The engine infers the managed datasets from the aliases the SQL\n// references — or pin an exact set with `datasetIds: [...]`.\n","ts","",[126,209,210,252,262,271,277,283],{"__ignoreMap":207},[211,212,215,219,223,227,230,233,236,239,242,245,249],"span",{"class":213,"line":214},"line",1,[211,216,218],{"class":217},"szBVR","const",[211,220,222],{"class":221},"sVt8B"," { ",[211,224,226],{"class":225},"sj4cs","columns",[211,228,229],{"class":221},", ",[211,231,232],{"class":225},"rows",[211,234,235],{"class":221}," } ",[211,237,238],{"class":217},"=",[211,240,241],{"class":217}," await",[211,243,244],{"class":221}," client.",[211,246,248],{"class":247},"sScJk","query",[211,250,251],{"class":221},"({\n",[211,253,255,258],{"class":213,"line":254},2,[211,256,257],{"class":221},"  sql,      ",[211,259,261],{"class":260},"sJ8bj","// alias-only SQL (never a physical table name)\n",[211,263,265,268],{"class":213,"line":264},3,[211,266,267],{"class":221},"  params,   ",[211,269,270],{"class":260},"// { column: value } filters, bound server-side\n",[211,272,274],{"class":213,"line":273},4,[211,275,276],{"class":221},"});\n",[211,278,280],{"class":213,"line":279},5,[211,281,282],{"class":260},"// The engine infers the managed datasets from the aliases the SQL\n",[211,284,286],{"class":213,"line":285},6,[211,287,288],{"class":260},"// references — or pin an exact set with `datasetIds: [...]`.\n",[18,290,291,292,295,296,299],{},"The SQL names ",[56,293,294],{},"managed datasets"," by alias, never raw tables — so the engine can resolve each alias, apply row-level filters, and cache, all server-side (pass ",[126,297,298],{},"datasetIds"," explicitly when you want to pin the exact set; inference is a convenience, not an authorization relaxation). That's deliberate. It's what keeps this \"the data layer your app is built around\" rather than \"a database connection string you could swap for any Postgres.\" Rent the pipeline, not just the storage.",[48,301,303],{"id":302},"where-this-fits","Where this fits",[26,305,306,312],{},[29,307,308,311],{},[22,309,310],{},"Non-developers, existing site, minutes not hours"," → the iframe door. Zero-code, live today.",[29,313,314,321],{},[22,315,316,317,320],{},"Developers who want the dashboard ",[56,318,319],{},"in"," their app, in their code"," → eject (preview). Own the frontend, rent the engine.",[18,323,324],{},"Two doors, one engine. Pick the one that matches how much of the frontend you want to own.",[48,326,328],{"id":327},"next-steps","Next steps",[18,330,331,332,339,340,345],{},"Try the zero-code door today, and read where the eject model is headed at ",[333,334,338],"a",{"href":335,"rel":336},"https://docs.vibedasher.com",[337],"nofollow","docs.vibedasher.com",". Or ",[333,341,344],{"href":342,"rel":343},"https://cloud.vibedasher.com/register",[337],"spin up an account and describe your first dashboard",".",[347,348,349],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":207,"searchDepth":254,"depth":254,"links":351},[352,353,354,355,356,357],{"id":50,"depth":254,"text":51},{"id":135,"depth":254,"text":136},{"id":179,"depth":254,"text":180},{"id":196,"depth":254,"text":197},{"id":302,"depth":254,"text":303},{"id":327,"depth":254,"text":328},"Product","2026-07-09","Classic embedded analytics locks you into a vendor's iframe. Headless BI flips it: own your dashboard code, rent the hard part — the ETL and query engine. The shadcn-meets-Supabase model for analytics.","md",{},true,"/blog/headless-bi-rent-the-engine",7,{"title":6,"description":360},"blog/headless-bi-rent-the-engine","x6U-GdLoyuVjbY1loT7IEqcy_VrZpVRtsSTz_MCkofo",{"id":370,"title":371,"author":7,"authorImage":8,"authorTitle":372,"body":373,"category":650,"date":359,"description":651,"extension":361,"image":8,"meta":652,"navigation":363,"path":653,"readTime":365,"seo":654,"stem":655,"__hash__":656},"content/blog/why-ai-codegen-cant-build-your-data-pipeline.md","Why AI Codegen Can't Build Your Data Pipeline","Engineering Team",{"type":11,"value":374,"toc":635},[375,378,381,388,392,395,414,417,421,428,433,440,444,447,454,461,465,476,480,491,499,510,517,521,524,538,594,601,608,612,619,622,633],[14,376,371],{"id":377},"why-ai-codegen-cant-build-your-data-pipeline",[18,379,380],{},"Ask an AI to build you a dashboard and it will. In one prompt you'll get a clean React component, a nice color palette, a responsive grid, maybe even a sensible chart type for your data. It looks done.",[18,382,383,384,387],{},"It is not done. It's the visible 10%. The other 90% — the part that makes the chart ",[56,385,386],{},"true"," tomorrow, at scale, for the right user — is the data pipeline. And that's the part AI codegen can't hand you.",[48,389,391],{"id":390},"what-ai-codegen-is-genuinely-great-at","What AI codegen is genuinely great at",[18,393,394],{},"Let's be fair, because the ceiling here is high and rising:",[26,396,397,400,411],{},[29,398,399],{},"Chart components — bar, line, area, the layout, the theming.",[29,401,402,403,406,407,410],{},"Glue code — a ",[126,404,405],{},"fetch",", a ",[126,408,409],{},"useEffect",", wiring state to controls.",[29,412,413],{},"One-shot transforms — \"group these rows by month and sum revenue\" on data you paste in.",[18,415,416],{},"If the data already exists, is already correct, and already fits in memory, AI codegen is a superpower. That's a real and useful envelope.",[48,418,420],{"id":419},"what-it-cant-build-and-why","What it can't build (and why)",[18,422,423,424,427],{},"A \"data pipeline\" isn't one thing an AI forgot to generate. It's a set of ",[56,425,426],{},"stateful, long-lived, correctness-critical systems"," that can't be one-shot into existence. Here's the gap, concretely.",[429,430,432],"h3",{"id":431},"connectors-that-survive-reality","Connectors that survive reality",[18,434,435,436,439],{},"Pulling from Stripe, Postgres, a warehouse, a SaaS API — the naive version is a day of work. The real version handles auth refresh, rate limits, pagination, schema drift when a source adds a column, and the source being down at 3am. An AI can scaffold a connector; it can't ",[56,437,438],{},"operate"," one. Connectors are maintenance, not code.",[429,441,443],{"id":442},"incremental-etl","Incremental ETL",[18,445,446],{},"The toy version reprocesses everything on every run. That works until you have real data volume, and then it's slow and expensive. Real ETL is incremental — it knows what changed since the last run, processes only that, and stays correct when a run fails halfway and retries. That's watermarks, idempotency, and state. There is no state for an AI to generate in a stateless prompt.",[429,448,450,451],{"id":449},"a-query-engine-not-a-select","A query engine, not a ",[126,452,453],{},"SELECT",[18,455,456,457,460],{},"Writing SQL is easy; ",[56,458,459],{},"serving"," it isn't. A query engine joins across managed datasets, plans the execution, enforces limits so one query can't take down the box, and returns typed, columnar results. It's a running system with a lifecycle, not a string an AI emits.",[429,462,464],{"id":463},"freshness-and-caching","Freshness and caching",[18,466,467,468,471,472,475],{},"Two questions every dashboard has to answer: ",[56,469,470],{},"is this current?"," and ",[56,473,474],{},"why did it take four seconds?"," Freshness means tracking when each dataset was last built and invalidating downstream when a source changes. Caching means repeat queries return instantly without re-running — and knowing exactly when a cached result is stale. These are runtime concerns with no source-code representation to generate.",[429,477,479],{"id":478},"row-level-security","Row-level security",[18,481,482,483,486,487,490],{},"The one that ends careers. Dashboard shows tenant A's revenue — fine. Now make sure tenant B, hitting the same dashboard, ",[56,484,485],{},"cannot"," see it, ever, even with a crafted request. That's row-level filtering enforced server-side, on every query, with fail-closed defaults. An AI writing a ",[126,488,489],{},"WHERE tenant_id = ..."," in a browser is not security — it's a filter the browser can rewrite. Real RLS has to live where the client can't reach it.",[48,492,494,495,498],{"id":493},"the-tell-its-all-state-and-runtime-not-code","The tell: it's all ",[56,496,497],{},"state and runtime",", not code",[18,500,501,502,505,506,509],{},"Notice the pattern. Everything AI codegen nails is ",[22,503,504],{},"stateless and static"," — a component, a transform, a snippet. Everything it can't do is ",[22,507,508],{},"stateful and runtime"," — connectors that run, ETL that remembers, caches that expire, security that's enforced on every request.",[18,511,512,513,516],{},"Codegen produces artifacts. Pipelines are ",[56,514,515],{},"systems that run",". You can't generate a system into existence with a prompt any more than you can generate a database's uptime. The chart is an artifact; the freshness of the number in it is a system.",[48,518,520],{"id":519},"so-rent-the-system-generate-the-artifact","So: rent the system, generate the artifact",[18,522,523],{},"This is exactly the Vibedasher split, and it's why the split exists.",[26,525,526,532],{},[29,527,528,531],{},[22,529,530],{},"Let AI generate the artifacts."," Your dashboard's charts, layout, and filter logic are code — so let your AI (Claude Code, Cursor) build and eject them into your app. You own that code. It's the part codegen is great at.",[29,533,534,537],{},[22,535,536],{},"Rent the running system."," The connectors, incremental ETL, query engine, freshness, caching, and row-level security are what Vibedasher operates. The whole thing surfaces through one call:",[202,539,541],{"className":204,"code":540,"language":206,"meta":207,"style":207},"const { columns, rows } = await client.query({\n  sql,      // alias-only SQL, composed from your controls\n  params,   // { column: value } filters, bound server-side\n});\n// Managed datasets are inferred from the SQL's aliases — or pinned\n// explicitly with `datasetIds: [...]`.\n",[126,542,543,567,574,580,584,589],{"__ignoreMap":207},[211,544,545,547,549,551,553,555,557,559,561,563,565],{"class":213,"line":214},[211,546,218],{"class":217},[211,548,222],{"class":221},[211,550,226],{"class":225},[211,552,229],{"class":221},[211,554,232],{"class":225},[211,556,235],{"class":221},[211,558,238],{"class":217},[211,560,241],{"class":217},[211,562,244],{"class":221},[211,564,248],{"class":247},[211,566,251],{"class":221},[211,568,569,571],{"class":213,"line":254},[211,570,257],{"class":221},[211,572,573],{"class":260},"// alias-only SQL, composed from your controls\n",[211,575,576,578],{"class":213,"line":264},[211,577,267],{"class":221},[211,579,270],{"class":260},[211,581,582],{"class":213,"line":273},[211,583,276],{"class":221},[211,585,586],{"class":213,"line":279},[211,587,588],{"class":260},"// Managed datasets are inferred from the SQL's aliases — or pinned\n",[211,590,591],{"class":213,"line":285},[211,592,593],{"class":260},"// explicitly with `datasetIds: [...]`.\n",[18,595,596,597,600],{},"Every hard thing above happens ",[56,598,599],{},"behind"," that call. The table aliases resolve to managed datasets and filtered subqueries under row-level security. The result comes from cache when it can. Freshness is tracked upstream. Your ejected code never reasons about any of it — it asks for rows and gets typed rows.",[18,602,603,604,607],{},"That's the point of renting an engine: not because you ",[56,605,606],{},"couldn't"," build a query planner and an incremental ETL layer and a caching tier, but because building and operating them is a team and a roadmap, not a prompt — and it's not what makes your product yours.",[48,609,611],{"id":610},"the-takeaway","The takeaway",[18,613,614,615,618],{},"AI codegen collapsed the cost of the frontend to near zero. It did ",[22,616,617],{},"not"," collapse the cost of the pipeline underneath — because that cost was never code. It was systems, state, and operations.",[18,620,621],{},"Generate the chart. Rent the engine that makes it true.",[18,623,624,625,628,629,345],{},"See how the engine surfaces to your code at ",[333,626,338],{"href":335,"rel":627},[337],", or ",[333,630,632],{"href":342,"rel":631},[337],"describe your first dashboard at cloud.vibedasher.com",[347,634,349],{},{"title":207,"searchDepth":254,"depth":254,"links":636},[637,638,646,648,649],{"id":390,"depth":254,"text":391},{"id":419,"depth":254,"text":420,"children":639},[640,641,642,644,645],{"id":431,"depth":264,"text":432},{"id":442,"depth":264,"text":443},{"id":449,"depth":264,"text":643},"A query engine, not a SELECT",{"id":463,"depth":264,"text":464},{"id":478,"depth":264,"text":479},{"id":493,"depth":254,"text":647},"The tell: it's all state and runtime, not code",{"id":519,"depth":254,"text":520},{"id":610,"depth":254,"text":611},"Engineering","AI can vibecode a beautiful chart in seconds. It cannot build the connectors, incremental ETL, query engine, freshness, caching, and row-level security underneath it. That gap is exactly what Vibedasher runs.",{},"/blog/why-ai-codegen-cant-build-your-data-pipeline",{"title":371,"description":651},"blog/why-ai-codegen-cant-build-your-data-pipeline","p4zUK36qVuqVN5rxuKj-NS1uqfGH79YlBKaQL93aJAI",1788706563931]