<?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: Alex</title>
    <description>The latest articles on DEV Community by Alex (@satlla).</description>
    <link>https://dev.to/satlla</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3891396%2Fa6f4e821-4b30-42e3-aa73-c6e6a43a5bc3.png</url>
      <title>DEV Community: Alex</title>
      <link>https://dev.to/satlla</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/satlla"/>
    <language>en</language>
    <item>
      <title>How I Built a SaaS for Vacation Rental Hosts: Stack &amp; Key Decisions</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Tue, 21 Apr 2026 22:56:08 +0000</pubDate>
      <link>https://dev.to/satlla/how-i-built-a-saas-for-vacation-rental-hosts-stack-key-decisions-303f</link>
      <guid>https://dev.to/satlla/how-i-built-a-saas-for-vacation-rental-hosts-stack-key-decisions-303f</guid>
      <description>&lt;p&gt;I manage vacation rentals inSpain. After a bad experience as a guest — 20 WhatsApp videos the night before a trip — I decided to build a  tool to fix the problem. That tool became Itineramio.                                                                                                &lt;/p&gt;

&lt;p&gt;Here's the stack I chose and why.                                                                                                                    &lt;/p&gt;

&lt;p&gt;The Stack&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Framework: Next.js 15 (App Router) — Server Components for fast mobile loading&lt;/li&gt;
&lt;li&gt;Database: PostgreSQL + Prisma — Type-safe ORM, great migrations&lt;/li&gt;
&lt;li&gt;Auth: JWT with jose (Edge) — Lightweight, works in Edge Runtime
&lt;/li&gt;
&lt;li&gt;Payments: Stripe — Industry standard, great docs&lt;/li&gt;
&lt;li&gt;Email: Resend — Simple API, good deliverability
&lt;/li&gt;
&lt;li&gt;Storage: Vercel Blob — Zero config, works with Next.js
&lt;/li&gt;
&lt;li&gt;UI: Tailwind + Radix UI — Fast to build, accessible by default
&lt;/li&gt;
&lt;li&gt;Rate limiting: Upstash Redis — Edge-compatible, pay-per-use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key Architecture Decisions                                &lt;/p&gt;

&lt;p&gt;App Router over Pages Router                                                                                                                         &lt;/p&gt;

&lt;p&gt;Guest-facing guides need to load fast — guests are often on roaming data or hotel WiFi. Server Components let me ship minimal JavaScript to the client while keeping the dashboard interactive where needed.&lt;/p&gt;

&lt;p&gt;JWT at the Edge&lt;/p&gt;

&lt;p&gt;I needed auth in middleware without cold starts. jsonwebtoken doesn't work in Edge Runtime, so I use jose for middleware verification and jsonwebtoken in API routes. Not ideal, but it works.&lt;/p&gt;

&lt;p&gt;Building VeriFactu Compliance Early&lt;br&gt;&lt;br&gt;
Spain is introducing VeriFactu in 2027 — invoicing software must maintain a SHA-256 hash chain, report to the tax authority, and generate QR codes. I built this from day one instead of retrofitting it later. Each invoice's hash includes the previous one, creating an immutable chain.&lt;/p&gt;

&lt;p&gt;AI Chatbot with Scoped Context                            &lt;/p&gt;

&lt;p&gt;The chatbot only knows what's in the property's manual. This keeps responses accurate — it won't hallucinate restaurant recommendations or make up appliance instructions. Scoped context beats general knowledge for this use case.&lt;/p&gt;

&lt;p&gt;Mistakes I Made                                           &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Skipping rate limiting — Bots found my public endpoints fast. Add rate limiting from the start, not after.
&lt;/li&gt;
&lt;li&gt;Silent form failures — React Hook Form's valueAsNumber returns NaN for empty fields. Zod accepts it as a number but fails validation silently.Always preprocess numeric inputs.
&lt;/li&gt;
&lt;li&gt;Over-engineering the loading state — I built a full-page spinner that ended up blocking all clicks across the app. Sometimes less is more.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Current Scale&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~460 API routes
&lt;/li&gt;
&lt;li&gt;128 database models&lt;/li&gt;
&lt;li&gt;100+ components&lt;/li&gt;
&lt;li&gt;6 cron jobs running on Vercel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Lesson                                                                                                                                           &lt;/p&gt;

&lt;p&gt;The best tools come from experiencing the problem yourself. I'd been a host for years but never understood the guest's frustration until I was one.  &lt;/p&gt;

&lt;p&gt;If you're curious: &lt;a href="https://www.itineramio.com" rel="noopener noreferrer"&gt;itineramio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>saas</category>
      <category>showdev</category>
      <category>startup</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
