<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Janni Hares</title>
    <description>The latest articles on DEV Community by Janni Hares (@decivo).</description>
    <link>https://dev.to/decivo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3877373%2Fef4b615b-8f21-4429-855c-5517f7e4bebc.png</url>
      <title>DEV Community: Janni Hares</title>
      <link>https://dev.to/decivo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/decivo"/>
    <language>en</language>
    <item>
      <title>12 things to check before you ship your vibe-coded app</title>
      <dc:creator>Janni Hares</dc:creator>
      <pubDate>Sat, 25 Jul 2026 15:16:54 +0000</pubDate>
      <link>https://dev.to/decivo/12-things-to-check-before-you-ship-your-vibe-coded-app-n0p</link>
      <guid>https://dev.to/decivo/12-things-to-check-before-you-ship-your-vibe-coded-app-n0p</guid>
      <description>&lt;p&gt;Getting an app to &lt;em&gt;work&lt;/em&gt; has stopped being the hard part. You describe what you want, Lovable or Bolt or v0 builds it, and forty minutes later there's something on a real URL that real people can click.&lt;/p&gt;

&lt;p&gt;The hard part moved. It's now everything between "it works" and "it survives contact with the internet."&lt;/p&gt;

&lt;p&gt;That gap isn't a vibe. It's measurable. &lt;a href="https://www.symbioticsec.ai/research" rel="noopener noreferrer"&gt;Symbiotic Security&lt;/a&gt; crawled 65,643 URLs and fully scanned 1,072 Supabase-backed vibe-coded apps in June 2026: 98% had at least one security issue, 16% had something critical. A separate &lt;a href="https://arxiv.org/abs/2606.23130" rel="noopener noreferrer"&gt;academic study by Deng et al.&lt;/a&gt; found that vibe-coded apps show &lt;em&gt;recurring&lt;/em&gt; vulnerability patterns that differ from the ones traditional codebases produce — meaning these aren't random mistakes, they're structural. And an Xint.io analysis reported by SecurityWeek turned up 434 exploitable flaws concentrated in secrets exposure, broken authorization and denial of service.&lt;/p&gt;

&lt;p&gt;Same handful of failure modes, over and over. Which is good news, because it means you can check for them in about fifteen minutes.&lt;/p&gt;

&lt;p&gt;Below is the list I actually walk through. Everything here you can run against your own domain with curl and browser devtools. No tooling required.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Is your &lt;code&gt;.env&lt;/code&gt; reachable over HTTP?
&lt;/h2&gt;

&lt;p&gt;The single most common catastrophic finding. It happens when the build output directory and the project root end up being the same thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yourapp.com/.env | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yourapp.com/.env.local | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yourapp.com/.env.production | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything other than &lt;code&gt;404&lt;/code&gt; is an emergency. Rotate every key in that file before you do anything else — assume it's already been scraped, because bots hit these paths constantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Is your &lt;code&gt;.git&lt;/code&gt; directory exposed?
&lt;/h2&gt;

&lt;p&gt;Worse than &lt;code&gt;.env&lt;/code&gt;, because it hands over your entire history including keys you &lt;em&gt;thought&lt;/em&gt; you'd removed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yourapp.com/.git/HEAD | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt;  https://yourapp.com/.git/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;HEAD&lt;/code&gt; returns 200, the whole repository is reconstructable by a stranger.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Which keys are sitting in your client bundle?
&lt;/h2&gt;

&lt;p&gt;Open devtools, go to Sources, and search across all files. You're looking for &lt;code&gt;sk-&lt;/code&gt;, &lt;code&gt;service_role&lt;/code&gt;, &lt;code&gt;SECRET&lt;/code&gt;, &lt;code&gt;PRIVATE_KEY&lt;/code&gt;, and long strings starting with &lt;code&gt;eyJ&lt;/code&gt; (those are JWTs).&lt;/p&gt;

&lt;p&gt;The nuance that trips people up: a Supabase &lt;em&gt;anon&lt;/em&gt; key in the browser is fine by design. A &lt;em&gt;service_role&lt;/em&gt; key is not — it bypasses row-level security entirely. AI assistants confuse the two constantly, because both are "the Supabase key" from the prompt's point of view.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Is row-level security actually on?
&lt;/h2&gt;

&lt;p&gt;Having an anon key in the browser is only safe if RLS is enabled on every table. Default-off is the trap. Go through your tables one by one and confirm policies exist. "I'll add policies later" is how the 16% happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Security headers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yourapp.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy|permissions-policy'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most vibe-coded deploys return none of these. The two that matter most immediately are &lt;code&gt;Content-Security-Policy&lt;/code&gt; (or at minimum &lt;code&gt;frame-ancestors&lt;/code&gt;) to stop clickjacking, and &lt;code&gt;Strict-Transport-Security&lt;/code&gt; so a downgrade attack can't strip your TLS.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Published source maps
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yourapp.com/_next/static/chunks/main.js.map | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source maps in production hand attackers your original, readable source — comments, internal function names, dead code paths and all. Turn them off in your build config, or restrict them to authenticated access.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Debug leftovers and orphan routes
&lt;/h2&gt;

&lt;p&gt;Grep your own bundle for &lt;code&gt;console.log&lt;/code&gt; and check whether anything sensitive is being printed on page load. Then try the routes nobody meant to ship: &lt;code&gt;/api/debug&lt;/code&gt;, &lt;code&gt;/api/test&lt;/code&gt;, &lt;code&gt;/admin&lt;/code&gt;, &lt;code&gt;/api/seed&lt;/code&gt;. AI-generated scaffolding loves to leave these behind, unauthenticated.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Rate limiting on auth and API endpoints
&lt;/h2&gt;

&lt;p&gt;Almost never present unless explicitly asked for. Without it, your login endpoint is a free credential-stuffing target and your LLM-backed API route is somebody else's free inference budget. Check whether your host gives you rate limiting at the edge — often it's a config flag you just haven't flipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Error messages that leak
&lt;/h2&gt;

&lt;p&gt;Trigger a failure deliberately: malformed JSON to a POST endpoint, a bad ID in a path parameter. If you get back a stack trace, a file path, or an ORM error naming your tables and columns, that's free reconnaissance for anyone probing you.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Untouched boilerplate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yourapp.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;title&amp;gt;|og:image|og:description'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it still says "Create Next App", or there's no Open Graph image, you're broadcasting that nobody reviewed this. It's not a vulnerability, but it changes how everything else about your product gets judged — including by the security researcher deciding whether you're worth poking at.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Trackers firing before consent
&lt;/h2&gt;

&lt;p&gt;Open the Network tab, hard-reload, and watch what leaves the page before you've clicked anything. If Google Analytics, Meta Pixel or a session recorder fires on load, you have a consent problem in the EU — and a "we didn't know it was there" problem generally, because AI-generated templates ship with analytics snippets baked in.&lt;/p&gt;

&lt;p&gt;Related and easy to miss: Google Fonts loaded at runtime from Google's CDN transmits visitor IP addresses to a third country. A German court ruled on exactly this in 2022 and it kicked off a wave of warning letters. Self-host your fonts. It's faster anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Imprint and privacy policy
&lt;/h2&gt;

&lt;p&gt;If you have users in Germany or Austria, an imprint is a legal requirement, not a nice-to-have, and the privacy policy has to actually describe what you're collecting. This is the check that costs nothing and gets skipped the most, because it's boring and nobody's prompt asked for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fifteen-minute version
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://yourapp.com"&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;path &lt;span class="k"&gt;in&lt;/span&gt; /.env /.env.local /.env.production /.git/HEAD /.git/config &lt;span class="se"&gt;\&lt;/span&gt;
            /api/debug /api/test /admin&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN$path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--- headers ---"&lt;/span&gt;
curl &lt;span class="nt"&gt;-sI&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;200&lt;/code&gt; in that first block is a finding. Every missing header in the second block is a gap. Run it against your own domain only — this is a self-audit, not a scanner to point at other people's sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with the results
&lt;/h2&gt;

&lt;p&gt;Sort into three buckets and be honest about which one you're in.&lt;/p&gt;

&lt;p&gt;Exposed secrets, an open &lt;code&gt;.git&lt;/code&gt;, or a service_role key in the bundle means &lt;strong&gt;stop&lt;/strong&gt;. Rotate keys, fix, redeploy, and don't announce anything until it's clean.&lt;/p&gt;

&lt;p&gt;Missing headers, source maps, debug routes and boilerplate metadata mean &lt;strong&gt;iterate&lt;/strong&gt; — real issues, fixable in an afternoon, not reasons to delay a soft launch to a small audience.&lt;/p&gt;

&lt;p&gt;Everything clean means &lt;strong&gt;go&lt;/strong&gt;, with the caveat that this is a point-in-time snapshot. The next AI-generated feature can reintroduce any of it, so re-run before each meaningful deploy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I work at &lt;a href="https://www.decivo.de" rel="noopener noreferrer"&gt;decivo&lt;/a&gt;, where we do exactly this kind of review for teams shipping AI-built products. We wrapped the outside-in portion of this checklist into a free scan called &lt;a href="https://www.decivo.de/de/rescue" rel="noopener noreferrer"&gt;Vibe Code Rescue&lt;/a&gt; — you paste a URL, it runs the external checks and gives you a Go / Iterate / Stop verdict. No signup, no code access, nothing stored. The manual checklist above covers the same ground if you'd rather do it yourself, which is genuinely fine by me.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>security</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>We Stopped Writing Code First. Here's Why Our Projects Got Better.</title>
      <dc:creator>Janni Hares</dc:creator>
      <pubDate>Mon, 13 Apr 2026 20:44:12 +0000</pubDate>
      <link>https://dev.to/decivo/we-stopped-writing-code-first-heres-why-our-projects-got-better-4871</link>
      <guid>https://dev.to/decivo/we-stopped-writing-code-first-heres-why-our-projects-got-better-4871</guid>
      <description>&lt;p&gt;There's a moment in every project where you realize you've been building the wrong thing.&lt;/p&gt;

&lt;p&gt;For us, it happened on a client project last year. Three weeks of clean code. Solid architecture. Tests passing. Then we showed it to actual users and watched them struggle with the core flow we thought was intuitive.&lt;/p&gt;

&lt;p&gt;We didn't have a code problem. We had a clarity problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern we kept seeing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're a lean software studio. We build prototypes and MVPs for startups and companies. And the pattern was always the same:&lt;br&gt;
Client comes in with an idea. Idea sounds great in a meeting. Everyone's excited. We start coding. Weeks later, something feels off. Users don't behave the way we expected. Features that seemed essential get ignored. The thing nobody asked for turns out to be the thing everyone actually needs.&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we changed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We stopped starting with code.&lt;br&gt;
Not permanently. We still write code every day. But we stopped making it the first thing we do.&lt;/p&gt;

&lt;p&gt;Now, before we open an editor, we do three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A clickable prototype that feels real
Not wireframes. Not sketches on a napkin. A Figma prototype that looks and feels like a real product. No backend, no database – but real enough that when you put it in front of someone, they try to use it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This takes us 2-3 days. It used to take weeks. AI tools like Figma Make help, but the key insight isn't the tooling – it's that a prototype answers questions a meeting never will.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Five conversations we don't want to have
We put the prototype in front of 5-8 people from the target audience. Not friends. Not the client's team. Actual potential users.
And we don't ask "do you like it?" – that's a useless question. Everyone says yes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We ask: "Show me how you'd do X." Then we shut up and watch.&lt;br&gt;
Where do they hesitate? Where do they tap the wrong thing? Where do they say "wait, what does this do?"&lt;br&gt;
Every moment of confusion is a feature we would have built wrong.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Actively looking for reasons it won't work
This is the counterintuitive one. We go into user conversations looking for the "no."
"I would never use this because..."
"This doesn't solve my actual problem because..."
"I already handle this with a spreadsheet and it's fine."
One honest "no" is worth more than ten polite "sounds interesting."
What happens after&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only after these three steps do we write code. And by then, something has shifted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The feature list is shorter (usually 40-50% shorter than the original spec)&lt;/li&gt;
&lt;li&gt;The core flow is different from what everyone assumed in the kickoff&lt;/li&gt;
&lt;li&gt;The client has seen real user reactions, not just our opinion&lt;/li&gt;
&lt;li&gt;We know why we're building each feature, not just what&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The numbers behind this&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This isn't just our experience. CB Insights analyzed hundreds of startup post-mortems and found that 42% failed because there was no market need. Not bad code. Not running out of money. Building something nobody wanted.&lt;/p&gt;

&lt;p&gt;The Startup Genome Project found that premature scaling – building too much before validating – kills 70% of startups. Teams that scale prematurely have 20x lower growth rates.&lt;/p&gt;

&lt;p&gt;Think about that: &lt;strong&gt;the biggest risk isn't writing bad code. It's writing good code for the wrong thing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"But we don't have time for validation"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the objection we hear most. And it's backwards.&lt;/p&gt;

&lt;p&gt;You don't have time to not validate.&lt;/p&gt;

&lt;p&gt;Two weeks of prototyping and user testing costs a fraction of what six months of building the wrong product costs. We've seen this play out multiple times: a client comes in wanting to build Feature X, we prototype it, test it, and discover that users actually need Feature Y. The prototype cost days. Building the wrong feature would have cost months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How AI changes this (and how it doesn't)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We use AI heavily. Claude Code writes our implementations. Figma Make generates design drafts. Gemini handles competitive research. AI is involved in 6 of our 12 workflow phases.&lt;/p&gt;

&lt;p&gt;But here's what AI doesn't do: it doesn't tell you whether you're solving the right problem.&lt;/p&gt;

&lt;p&gt;AI makes building faster. That's genuinely useful. But if your direction is wrong, AI just gets you to the wrong destination more efficiently. The code will be clean. The architecture will be solid. And the product will still miss.&lt;/p&gt;

&lt;p&gt;That's why every AI step in our workflow has a human gate after it. AI proposes, humans decide. Not because AI is bad – because speed without direction is waste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The uncomfortable truth&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most of the value we deliver to clients isn't code. It's the moment in a user test when someone says "I don't need this" and saves the client from building it.&lt;/p&gt;

&lt;p&gt;That moment is uncomfortable. Nobody wants to hear that their idea needs changing. But it's infinitely cheaper to hear it from a prototype test than from an empty dashboard after launch.&lt;br&gt;
If you're about to start building something – a side project, a client project, a startup – try this before you write your first line of code:&lt;/p&gt;

&lt;p&gt;Build a clickable prototype (Figma, even just screenshots stitched together)&lt;br&gt;
Put it in front of 5 people who have the problem you're solving&lt;br&gt;
Ask them to use it, not whether they like it&lt;br&gt;
Listen for the "no"&lt;/p&gt;

&lt;p&gt;It might save you months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We're the team of &lt;a href="https://www.decivo.de" rel="noopener noreferrer"&gt;decivo&lt;/a&gt;. We build prototypes and MVPs for startups – with an AI-powered validated workflow&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>mvp</category>
      <category>webdev</category>
      <category>startup</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
