<?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: Doktouri</title>
    <description>The latest articles on DEV Community by Doktouri (@agenyc).</description>
    <link>https://dev.to/agenyc</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%2F4021810%2Fe291b358-0793-4cc3-a688-6c191c4ba7be.png</url>
      <title>DEV Community: Doktouri</title>
      <link>https://dev.to/agenyc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agenyc"/>
    <language>en</language>
    <item>
      <title>Fixed price vs time &amp; materials</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:29:54 +0000</pubDate>
      <link>https://dev.to/agenyc/fixed-price-vs-time-materials-46mm</link>
      <guid>https://dev.to/agenyc/fixed-price-vs-time-materials-46mm</guid>
      <description>&lt;p&gt;The contract you sign with a software team shapes the project as much as the code does. It decides who carries the risk, how welcome change is, and which incentives quietly pull on both sides. Fixed price and time &amp;amp; materials are the two dominant models, and neither is universally better — the right one depends entirely on how much uncertainty your project carries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixed price: certainty bought with rigidity
&lt;/h2&gt;

&lt;p&gt;In a fixed-price deal you agree a defined scope for a defined amount. The team carries the risk of overrun. That certainty is appealing, and for the right project it's the correct choice — but it comes with strings.&lt;/p&gt;

&lt;p&gt;Fixed price only works when scope is genuinely well understood up front. To protect against the unknowns, the team prices in a risk buffer, so you often pay more for the certainty. And because any change threatens their margin, change requests turn into negotiations. The model quietly incentivizes &lt;em&gt;matching the spec&lt;/em&gt;, not &lt;em&gt;finding the best solution&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Fixed price fits when: the scope is clear and stable, you need budget certainty, and the work is well-trodden rather than exploratory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time &amp;amp; materials: flexibility that demands trust
&lt;/h2&gt;

&lt;p&gt;Under time &amp;amp; materials you pay for the effort spent, usually at an agreed rate. Scope can flex as you learn. The client carries the budget risk, but gains the freedom to change direction without renegotiating a contract every time.&lt;/p&gt;

&lt;p&gt;The upside is honesty of incentives: the team is free to build the &lt;em&gt;right&lt;/em&gt; thing rather than defend a fixed scope, and you're not paying a padded buffer for risk that may never materialize. The downside is that it requires trust and active involvement — an absent client on a T&amp;amp;M contract can watch costs drift.&lt;/p&gt;

&lt;p&gt;Time &amp;amp; materials fits when: scope is uncertain or evolving, discovery is ongoing, and you want the flexibility to steer as you learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  The incentive question nobody asks
&lt;/h2&gt;

&lt;p&gt;Look past the price to what each model rewards. Fixed price rewards &lt;em&gt;delivering the agreed scope&lt;/em&gt; — even if you learn mid-build that a different scope would serve users better. Time &amp;amp; materials rewards &lt;em&gt;doing valuable work&lt;/em&gt; — but only pays off if you stay engaged enough to keep that work pointed in the right direction. Match the incentive to the reality of your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caps, phases, and hybrids
&lt;/h2&gt;

&lt;p&gt;The models aren't a hard binary. Sensible middle grounds exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capped T&amp;amp;M&lt;/strong&gt; — bill for time but agree a ceiling, sharing the risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixed-price discovery, then T&amp;amp;M build&lt;/strong&gt; — nail down scope cheaply first, then flex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phased fixed price&lt;/strong&gt; — fix the price of a well-understood slice, re-scope for the next.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These often beat either pure model, especially early when uncertainty is highest.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose in one question
&lt;/h2&gt;

&lt;p&gt;Ask: &lt;em&gt;how well do I understand what needs to be built?&lt;/em&gt; If the answer is "very well, and it won't change much," fixed price gives you certainty. If it's "we'll learn a lot as we go," time &amp;amp; materials — ideally capped — keeps you free to build the right thing.&lt;/p&gt;

&lt;p&gt;At Doktouri we structure engagements around the actual uncertainty in your project, not a one-size template. If you're weighing how to contract a build, &lt;a href="https://dev.to/#contact"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/fixed-price-vs-time-materials" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>product</category>
      <category>contracts</category>
      <category>agency</category>
      <category>budget</category>
    </item>
    <item>
      <title>Is Expo production-ready in 2026?</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:29:20 +0000</pubDate>
      <link>https://dev.to/agenyc/is-expo-production-ready-in-2026-2f1k</link>
      <guid>https://dev.to/agenyc/is-expo-production-ready-in-2026-2f1k</guid>
      <description>&lt;p&gt;The old advice was "start with Expo, then eject when you hit its limits." That advice is out of date. Expo has evolved from a convenient sandbox into the recommended, production-grade way to build React Native apps — and the "eject" concept is essentially gone. If you're evaluating it on reputation from a few years ago, here's the honest 2026 picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;Two developments moved Expo from prototype tool to production platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The dev client and config plugins.&lt;/strong&gt; You're no longer limited to a fixed set of built-in native modules. Any native library works, and config plugins let you customize the native project declaratively. The hard wall that used to force ejection is gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS (Expo Application Services).&lt;/strong&gt; Cloud build, submission, and — crucially — &lt;strong&gt;over-the-air updates&lt;/strong&gt; turned Expo into an end-to-end delivery pipeline, not just a starter kit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The modern flow is "prebuild," which generates native projects you fully control while keeping Expo's tooling. There's no dramatic one-way eject anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Expo genuinely shines
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EAS Build&lt;/strong&gt; removes the misery of local iOS/Android build environments, certificates, and signing — it builds in the cloud&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS Update&lt;/strong&gt; ships JavaScript-only fixes over the air, bypassing store review for many changes (within platform rules)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS Submit&lt;/strong&gt; automates uploads to both stores&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;file-based router&lt;/strong&gt; (Expo Router) brings clean, typed navigation&lt;/li&gt;
&lt;li&gt;Excellent developer experience and fast iteration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most React Native apps, this toolchain saves weeks of undifferentiated setup and maintenance work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real limits
&lt;/h2&gt;

&lt;p&gt;It's not magic. Be aware of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fully custom native code.&lt;/strong&gt; You &lt;em&gt;can&lt;/em&gt; write and integrate any native module via config plugins, but if your app is native-heavy with lots of bespoke native work, you take on real complexity — Expo helps less there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost at scale.&lt;/strong&gt; EAS is generous but paid past its free tier; heavy build volume has a cost. You can also self-host builds if needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bleeding-edge native APIs.&lt;/strong&gt; A brand-new OS API without a community module yet may need you to write the native binding yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App size.&lt;/strong&gt; Expo apps can be slightly larger, though this has improved and is rarely a real problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are blockers for the vast majority of apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use it — and when to think twice
&lt;/h2&gt;

&lt;p&gt;Use Expo (this is our default for new React Native apps) when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to move fast and avoid native build-environment pain&lt;/li&gt;
&lt;li&gt;Over-the-air updates and streamlined store submission are valuable&lt;/li&gt;
&lt;li&gt;Your app is standard-to-moderately-custom — the common case&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think harder when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app is dominated by heavy, custom native code and hardware integration&lt;/li&gt;
&lt;li&gt;You have very specific native performance or platform requirements Expo doesn't yet smooth over&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The verdict
&lt;/h2&gt;

&lt;p&gt;Expo is production-ready and then some. Major apps ship on it, the eject-fear is obsolete, and for most teams it's the fastest path to a maintainable React Native app. Reach past it only when your product is genuinely native-heavy in ways the tooling can't streamline.&lt;/p&gt;

&lt;p&gt;If you're starting a React Native app and want it built on a modern Expo/EAS pipeline that ships fast and updates over the air, &lt;a href="https://dev.to/#contact"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/expo-for-production" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>expo</category>
      <category>reactnative</category>
      <category>production</category>
    </item>
    <item>
      <title>E-commerce conversion optimization that moves revenue</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:23:40 +0000</pubDate>
      <link>https://dev.to/agenyc/e-commerce-conversion-optimization-that-moves-revenue-1on</link>
      <guid>https://dev.to/agenyc/e-commerce-conversion-optimization-that-moves-revenue-1on</guid>
      <description>&lt;p&gt;Most conversion advice is a pile of tips with no ranking, so teams burn weeks A/B testing button colors while their checkout leaks money on mobile. Conversion optimization works when you fix the biggest leaks first. This is the order we'd actually work in — highest revenue impact at the top, vanity tweaks at the bottom.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Fix checkout friction first
&lt;/h2&gt;

&lt;p&gt;Checkout is where intent turns into revenue, and where most of it dies. Every extra field, forced account creation, or surprise cost at the last step sheds buyers who were ready to pay.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Offer &lt;strong&gt;guest checkout&lt;/strong&gt; — never force account creation before purchase.&lt;/li&gt;
&lt;li&gt;Show total cost, including shipping, as early as possible. Surprise fees are the top abandonment cause.&lt;/li&gt;
&lt;li&gt;Support &lt;strong&gt;Apple Pay&lt;/strong&gt;, &lt;strong&gt;Google Pay&lt;/strong&gt;, and &lt;strong&gt;Shop Pay&lt;/strong&gt; so returning buyers skip the form entirely.&lt;/li&gt;
&lt;li&gt;Keep it to as few steps as the flow allows, with a visible progress indicator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is almost always the single highest-leverage area. Start here.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Make the store fast
&lt;/h2&gt;

&lt;p&gt;Speed is conversion. A store that takes several seconds to become interactive loses buyers before they see a product. Chase &lt;strong&gt;Core Web Vitals&lt;/strong&gt; — LCP, INP, CLS — as a revenue metric, not a technical one.&lt;/p&gt;

&lt;p&gt;Compress and lazy-load images, ship less JavaScript, and serve from a CDN. On mobile especially, every second of load time is measurable lost revenue.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Earn trust above the fold
&lt;/h2&gt;

&lt;p&gt;Shoppers decide fast whether a store is legitimate. Reduce that friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real product photography and clear, honest descriptions.&lt;/li&gt;
&lt;li&gt;Reviews and ratings on product pages.&lt;/li&gt;
&lt;li&gt;Visible return policy, shipping timelines, and support contact.&lt;/li&gt;
&lt;li&gt;Security and payment badges near the buy button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trust signals don't feel like CRO, but they move the numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Design for the thumb
&lt;/h2&gt;

&lt;p&gt;Most traffic is mobile, yet many stores are still designed desktop-first. Tap targets should be large, the add-to-cart button should stay reachable, and forms should use the right mobile keyboards. Test the entire purchase on a real phone, on real mobile data — not just a resized browser window.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Recover the carts you lose
&lt;/h2&gt;

&lt;p&gt;Not every abandonment is preventable. Win some back with automated abandoned-cart emails and, where appropriate, exit-intent offers. A well-timed reminder recovers a meaningful slice of otherwise-lost orders at almost no cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure like it's revenue, not opinions
&lt;/h2&gt;

&lt;p&gt;Instrument the funnel — product view, add to cart, checkout start, purchase — so you can see exactly where buyers drop. Change one thing, measure the segment it affects, and keep what wins. Optimization without measurement is just redecorating.&lt;/p&gt;

&lt;p&gt;At Doktouri we treat conversion as an engineering problem: measurable, testable, and tied to revenue. If your store gets traffic but not enough orders, &lt;a href="https://dev.to/#contact"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/ecommerce-conversion-optimization" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>cro</category>
      <category>conversion</category>
      <category>ux</category>
    </item>
    <item>
      <title>Docker for developers</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:23:07 +0000</pubDate>
      <link>https://dev.to/agenyc/docker-for-developers-25ka</link>
      <guid>https://dev.to/agenyc/docker-for-developers-25ka</guid>
      <description>&lt;p&gt;"Works on my machine" is the most expensive sentence in software. &lt;strong&gt;Docker&lt;/strong&gt; exists to kill it. At its core, Docker packages your application together with everything it needs to run — the runtime, libraries, and system dependencies — into a single portable unit that behaves identically on your laptop, a teammate's machine, and production. Once the concepts click, it stops feeling like mysterious DevOps magic and becomes a tool you reach for daily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Images and containers: the core idea
&lt;/h2&gt;

&lt;p&gt;Two words do most of the work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;image&lt;/strong&gt; is a blueprint — a read-only snapshot of your app and its environment, built from a &lt;code&gt;Dockerfile\&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;container&lt;/strong&gt; is a running instance of that image — a lightweight, isolated process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The analogy: an image is like a class, a container is like an object you instantiate from it. You build an image once and run many containers from it. Unlike a virtual machine, containers share the host kernel, so they start in milliseconds and use a fraction of the resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand layers, and your builds get fast
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;Dockerfile\&lt;/code&gt; is a list of instructions, and each one creates a &lt;strong&gt;layer&lt;/strong&gt; that Docker caches. This is the single most useful thing to understand for a good workflow: order your instructions from least to most frequently changing.&lt;/p&gt;

&lt;p&gt;In practice, copy your dependency manifest and install dependencies &lt;em&gt;before&lt;/em&gt; copying your application code:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;&lt;br&gt;
COPY package.json package-lock.json ./&lt;br&gt;
RUN npm ci&lt;br&gt;
COPY . .&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Because your source changes far more often than your dependencies, Docker reuses the cached install layer on every rebuild and only re-runs the fast steps. Get this order wrong and every one-line change reinstalls everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep images small with multi-stage builds
&lt;/h2&gt;

&lt;p&gt;Big images are slow to push, pull, and deploy — and they carry a larger attack surface. &lt;strong&gt;Multi-stage builds&lt;/strong&gt; let you compile or bundle in a fat "builder" stage, then copy only the finished artifacts into a slim final image. Start from a minimal base like an &lt;code&gt;alpine\&lt;/code&gt; or &lt;code&gt;slim\&lt;/code&gt; variant, and don't ship your build tools to production. A lean image is a faster, safer image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compose ties the whole app together
&lt;/h2&gt;

&lt;p&gt;Real apps aren't one process. You've got your API, a &lt;strong&gt;PostgreSQL&lt;/strong&gt; database, maybe &lt;strong&gt;Redis&lt;/strong&gt; for caching. &lt;strong&gt;Docker Compose&lt;/strong&gt; describes all of them in a single &lt;code&gt;docker-compose.yml\&lt;/code&gt; and starts them together with one command. A new developer clones the repo, runs &lt;code&gt;docker compose up\&lt;/code&gt;, and has the entire stack running locally in minutes — no page of setup instructions, no version mismatches, no "did you install Postgres 16?" This alone justifies adopting Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it pays off most
&lt;/h2&gt;

&lt;p&gt;The workflow that makes teams faster looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define the environment once in a &lt;code&gt;Dockerfile\&lt;/code&gt; and &lt;code&gt;docker-compose.yml\&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Every developer runs the identical stack locally — no drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI&lt;/strong&gt; builds and tests the same image you'll deploy.&lt;/li&gt;
&lt;li&gt;That exact image ships to production.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same bytes run everywhere, so a whole category of environment bugs simply stops existing. You don't need to master every Docker flag to get this benefit — the fundamentals here cover the vast majority of real development.&lt;/p&gt;

&lt;p&gt;If your onboarding still involves a wiki page of setup steps, &lt;a href="https://dev.to/#contact"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/docker-for-developers" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>docker</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>Design systems for startups</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:16:42 +0000</pubDate>
      <link>https://dev.to/agenyc/design-systems-for-startups-25ed</link>
      <guid>https://dev.to/agenyc/design-systems-for-startups-25ed</guid>
      <description>&lt;p&gt;A design system sounds like something you earn later — a luxury for companies big enough to have a design-systems team. For a startup that's exactly backwards. A right-sized system is what lets three people ship a coherent product fast, without re-deciding the same button padding on every screen. The trap isn't building one too early; it's building a heavyweight one you don't need. The answer is to start tiny and grow it only as it earns its keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with tokens, not components
&lt;/h2&gt;

&lt;p&gt;Before any component library, agree on the primitives everything else is built from — the &lt;strong&gt;design tokens&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A small color palette with defined roles (primary, surface, text, danger).&lt;/li&gt;
&lt;li&gt;A type scale — a handful of sizes, not a dozen.&lt;/li&gt;
&lt;li&gt;A spacing scale, so margins and padding come from a set, not from guesses.&lt;/li&gt;
&lt;li&gt;Consistent border radius and shadow values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tokens are cheap to define and pay off immediately: every screen built from the same primitives looks like the same product, automatically. This is the highest-leverage step, and most teams skip straight past it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build components only when you repeat yourself
&lt;/h2&gt;

&lt;p&gt;Don't sit down to design fifty components. Build one the second time you copy-paste it. The button, input, card, and modal you use everywhere are worth standardizing. The one-off nobody reuses is not. Let real repetition, not an imagined future, decide what becomes a component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use tooling that fits your size
&lt;/h2&gt;

&lt;p&gt;You don't need a bespoke pipeline. Lean on what already exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A component library like &lt;strong&gt;shadcn/ui&lt;/strong&gt; or &lt;strong&gt;Radix&lt;/strong&gt; gives you accessible, unstyled primitives to theme with your tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; maps naturally onto a token system through its config.&lt;/li&gt;
&lt;li&gt;Keep tokens in one place — CSS variables or a shared config — so a single change propagates everywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is leverage, not a platform. If your design system needs a maintainer, it's too big for your stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Document just enough
&lt;/h2&gt;

&lt;p&gt;A full documentation site is overkill early. What you need is a single source of truth — a page, a &lt;strong&gt;Storybook&lt;/strong&gt;, or a shared file — that answers "what do we already have and how do I use it?" so people reuse instead of reinventing. Documentation that nobody maintains is worse than a short one that's always current.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let it grow with the product
&lt;/h2&gt;

&lt;p&gt;A startup design system should feel slightly incomplete, always. You add to it as real needs appear, not in anticipation of them. When a pattern shows up three times, promote it into the system. When something in the system stops being used, remove it. It's a living tool, not a monument.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consistency is a speed feature
&lt;/h2&gt;

&lt;p&gt;The reason to do any of this isn't aesthetics — it's velocity. When primitives are decided and components are reusable, designers and developers stop re-litigating the basics and spend their time on the parts of the product that actually differentiate you. A good design system makes a small team feel larger.&lt;/p&gt;

&lt;p&gt;At Doktouri we build right-sized design systems that make startup teams faster without the overhead of a big-company setup. If your product's UI is drifting as you grow, &lt;a href="https://dev.to/#contact"&gt;talk to us&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/design-systems-for-startups" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>product</category>
      <category>designsystem</category>
      <category>ui</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Database indexing explained</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:16:08 +0000</pubDate>
      <link>https://dev.to/agenyc/database-indexing-explained-4pio</link>
      <guid>https://dev.to/agenyc/database-indexing-explained-4pio</guid>
      <description>&lt;p&gt;Almost every "the database is slow" complaint we investigate comes down to a missing or misused index. Indexing is the highest-leverage performance skill a developer can have, and yet it's often treated as arcane. It isn't. Once you understand what an index actually is and how to read a query plan, you can turn a ten-second query into a ten-millisecond one on purpose instead of by luck.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an index actually is
&lt;/h2&gt;

&lt;p&gt;Think of an index like the index at the back of a book. Without it, finding every mention of a topic means reading every page — a &lt;strong&gt;sequential scan&lt;/strong&gt;. With it, you jump straight to the right pages. In a database, an index is a separate, sorted data structure that lets the engine find rows without examining the whole table.&lt;/p&gt;

&lt;p&gt;Most indexes are &lt;strong&gt;B-trees&lt;/strong&gt;, which keep values sorted so the database can binary-search them. That's why a B-tree index speeds up equality (&lt;code&gt;=\&lt;/code&gt;), ranges (&lt;code&gt;&amp;lt;\&lt;/code&gt;, &lt;code&gt;&amp;gt;\&lt;/code&gt;, &lt;code&gt;BETWEEN\&lt;/code&gt;), sorting, and prefix matches — but does nothing for a leading-wildcard &lt;code&gt;LIKE '%foo'\&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Index what you filter, join, and sort on
&lt;/h2&gt;

&lt;p&gt;The rule of thumb: any column that regularly appears in a &lt;code&gt;WHERE\&lt;/code&gt; clause, a &lt;code&gt;JOIN\&lt;/code&gt; condition, or an &lt;code&gt;ORDER BY\&lt;/code&gt; is a candidate for an index. Foreign keys are almost always worth indexing, since you'll join on them constantly.&lt;/p&gt;

&lt;p&gt;For queries that filter on multiple columns, a &lt;strong&gt;composite index&lt;/strong&gt; on several columns together beats separate single-column indexes. But column order matters: an index on &lt;code&gt;(tenant_id, created_at)\&lt;/code&gt; helps queries filtering by &lt;code&gt;tenant_id\&lt;/code&gt; alone or by both — but not queries filtering by &lt;code&gt;created_at\&lt;/code&gt; alone. Put the most selective, most-often-filtered column first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn to read EXPLAIN
&lt;/h2&gt;

&lt;p&gt;You never have to guess whether an index is used. In &lt;strong&gt;PostgreSQL&lt;/strong&gt;, run &lt;code&gt;EXPLAIN ANALYZE\&lt;/code&gt; on your query and read the plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Seq Scan&lt;/strong&gt; on a large table in a hot query is a red flag — the database is reading everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index Scan&lt;/strong&gt; or &lt;strong&gt;Index Only Scan&lt;/strong&gt; means your index is doing its job.&lt;/li&gt;
&lt;li&gt;The reported row estimates versus actual rows tell you whether the planner's statistics are accurate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make &lt;code&gt;EXPLAIN ANALYZE\&lt;/code&gt; a reflex before and after adding an index. It turns performance work from folklore into measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Indexes aren't free
&lt;/h2&gt;

&lt;p&gt;It's tempting to index everything, but each index has a cost. Indexes take disk space, and — more importantly — every &lt;code&gt;INSERT\&lt;/code&gt;, &lt;code&gt;UPDATE\&lt;/code&gt;, and &lt;code&gt;DELETE\&lt;/code&gt; has to update every affected index. Over-indexing a write-heavy table slows down exactly the operations you care about.&lt;/p&gt;

&lt;p&gt;So index deliberately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add indexes to support real, measured slow queries — not hypothetical ones.&lt;/li&gt;
&lt;li&gt;Periodically drop indexes that nothing uses (Postgres tracks index usage stats).&lt;/li&gt;
&lt;li&gt;Watch for redundant indexes where a composite index already covers a single-column one.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Build the habit
&lt;/h2&gt;

&lt;p&gt;The whole discipline fits in one loop: write the query, run &lt;code&gt;EXPLAIN ANALYZE\&lt;/code&gt;, add the index the plan is begging for, and measure again. Do that consistently and slow-database fire drills mostly disappear from your life. The teams that struggle aren't missing some secret — they've just never looked at a query plan.&lt;/p&gt;

&lt;p&gt;If your app is crawling and you suspect the database, &lt;a href="https://dev.to/#contact"&gt;talk to us&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/database-indexing-explained" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>database</category>
      <category>postgres</category>
      <category>performance</category>
    </item>
    <item>
      <title>Custom e-commerce vs Shopify</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:07:41 +0000</pubDate>
      <link>https://dev.to/agenyc/custom-e-commerce-vs-shopify-4lad</link>
      <guid>https://dev.to/agenyc/custom-e-commerce-vs-shopify-4lad</guid>
      <description>&lt;p&gt;Most brands should start on Shopify. That's not a concession — it's the fastest way to a working store with payments, tax, shipping, and a checkout that converts. The real question isn't whether Shopify is good; it's whether your specific business hits a wall that only a custom build can clear. Answer that honestly and you avoid the two expensive mistakes: premature engineering (rebuilding solved problems to feel in control) and platform lock-in you've outgrown (paying in lost margin and blocked roadmap to avoid a migration). This is a decision framework, not a verdict — because the right answer genuinely depends on your business.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Shopify does better than you will
&lt;/h2&gt;

&lt;p&gt;Shopify's checkout is battle-tested across millions of orders and continuously optimized by a team larger than most companies. Fraud tooling, PCI compliance, one-click &lt;strong&gt;Shop Pay&lt;/strong&gt;, dozens of payment methods, address validation, and abandoned-cart recovery come essentially free. Rebuilding that from scratch is many months of work that adds zero differentiation — nobody buys from you &lt;em&gt;because&lt;/em&gt; you wrote your own checkout, and a checkout bug costs you real orders.&lt;/p&gt;

&lt;p&gt;Beyond checkout, you're renting a whole operational stack: inventory management, order routing, a mature app ecosystem, tax calculation across jurisdictions, and hosting that survives a Black Friday spike without you touching a server. That last point is easy to underrate — the day your product goes viral or a campaign lands, Shopify absorbs a 50x traffic spike automatically, while a custom store meets that same moment as an on-call incident. Platforms also ship improvements you inherit for free: new payment methods, checkout optimizations, fraud-model updates, compliance changes across regions. On a custom build, every one of those is a ticket in your backlog.&lt;/p&gt;

&lt;p&gt;For a catalog of a few thousand SKUs and standard fulfillment, Shopify plus a few well-chosen apps will outperform anything a small team ships in a quarter. If your storefront is a fairly conventional catalog-and-cart, stay on Shopify and put your energy into product, brand, and acquisition — the things that actually grow revenue.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signals that you've outgrown it
&lt;/h2&gt;

&lt;p&gt;Custom starts to pay off when your business logic stops fitting the template. Watch for these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complex pricing or configuration&lt;/strong&gt; — quote-based B2B, per-customer catalogs, volume tiers, or products assembled from many interdependent options (a configurator). Shopify's variant model caps out, and the app workarounds get brittle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App sprawl&lt;/strong&gt; — you're paying for a dozen apps that overlap, conflict, slow the store down, and still don't do quite what you need. The monthly app bill plus the maintenance burden of keeping them compatible starts to rival a custom feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Margin at scale&lt;/strong&gt; — at high GMV, platform fees plus transaction fees (especially if you're not on Shopify Payments) become a real line item. At tens of millions in GMV, reclaiming those points can fund the engineering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique fulfillment or inventory&lt;/strong&gt; — multi-warehouse routing, marketplace/multi-vendor logic, complex subscriptions, rentals, or bundles with rules no app models cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep systems integration&lt;/strong&gt; — a custom ERP, a specialized 3PL, or an internal tool that Shopify's APIs can only awkwardly reach.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of these alone is rarely enough to justify a rebuild — there's usually an app or a workaround. Two or three together, each costing you real money or blocking real revenue, is a genuine signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Score it honestly before you commit
&lt;/h2&gt;

&lt;p&gt;Founders talk themselves into custom builds because building feels like progress, and out of them because migration feels scary. Neither instinct is a business case. Before you spend a dollar, run a blunt scoring exercise across the dimensions that actually decide this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Control&lt;/strong&gt; — how much of the customer experience does the platform prevent you from shaping? On a conventional store, very little. On a novel one, a lot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost curve&lt;/strong&gt; — plot your platform, app, and transaction fees against projected GMV for the next 24 months. Custom flips from wasteful to smart at a specific revenue point; find yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt; — Shopify ships you a feature today; custom ships it in a sprint. Early on, speed usually wins outright.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk&lt;/strong&gt; — a custom checkout is your uptime, your PCI scope, your incident at midnight. Price that risk in, don't wish it away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team&lt;/strong&gt; — do you have (or will you retain) the engineering capacity to own a store &lt;em&gt;forever&lt;/em&gt;, not just ship one?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If custom only wins on "control" and loses on the other four, you have your answer. The brands that replatform successfully can point to a specific, quantified constraint the platform imposes and a specific number the rebuild unlocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The middle path most teams miss
&lt;/h2&gt;

&lt;p&gt;It's not binary, and treating it as binary is the most common mistake we see. &lt;strong&gt;Headless&lt;/strong&gt; commerce lets you keep a commerce engine — Shopify via its Storefront API, or an open engine like Medusa or commercetools — as the backend, while building a fully custom &lt;strong&gt;React&lt;/strong&gt; or &lt;strong&gt;Next.js&lt;/strong&gt; front end. You get complete design and performance freedom, the SEO benefits of &lt;a href="https://dev.to/blog/server-side-rendering-seo"&gt;server-side rendering&lt;/a&gt;, and pages that hit the &lt;a href="https://dev.to/blog/core-web-vitals-guide"&gt;Core Web Vitals&lt;/a&gt; targets that both convert and rank — all without rebuilding checkout, inventory, or payments.&lt;/p&gt;

&lt;p&gt;For many growing brands this is the right answer: custom where it differentiates (the storefront experience, the content, the speed), managed where it doesn't (the money-touching core). Our &lt;a href="https://dev.to/blog/headless-commerce-guide"&gt;headless commerce guide&lt;/a&gt; walks through the architecture and the trade-offs in depth. The catch is real, though — headless means you now own the front end's performance, deployment, and complexity, so it's a step up in engineering commitment, not a free lunch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a custom build actually costs
&lt;/h2&gt;

&lt;p&gt;A custom store isn't just the initial build, and founders who budget only for the launch get an unpleasant surprise. You own security patches, dependency upgrades, payment-processor API changes, tax-rule updates, PCI scope, and uptime — forever. When a payment provider deprecates an API or a new tax rule lands, that's your engineering time, on your schedule, at your cost. Budget for a maintenance retainer, not just a launch, and staff for the incident at 2 a.m. during your biggest sale.&lt;/p&gt;

&lt;p&gt;The math works when custom capability drives revenue or reclaims margin that comfortably exceeds that ongoing cost — and rarely before. If you can't point to a specific number the custom build unlocks, you're probably rebuilding for control, not returns.&lt;/p&gt;

&lt;p&gt;There's also an opportunity cost that never shows up on the invoice. Every engineer-month spent rebuilding a checkout that Shopify already gives you for free is a month not spent on the thing only your team can build — the feature that differentiates you, the acquisition channel that grows you. For an early brand, that's usually the most expensive line of all, precisely because it's invisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't replatform to fix a problem a platform can solve
&lt;/h2&gt;

&lt;p&gt;The most common regret we see isn't a custom build that failed technically — it's a custom build that solved a problem the brand could have solved on Shopify for a fraction of the cost. Slow store? That's usually a theme, an app-bloat, or an image problem, not a platform ceiling. Can't get the design you want? A headless front end fixes that without touching commerce. Conversion stalling? That's a merchandising and UX problem the &lt;a href="https://dev.to/blog/ecommerce-conversion-optimization"&gt;conversion optimization&lt;/a&gt; playbook addresses far more cheaply than an engineering team. Exhaust the platform's real limits before you conclude you've hit them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple decision rule
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start on Shopify.&lt;/strong&gt; Validate the business. Ship fast. Spend on brand and acquisition, not infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move to headless&lt;/strong&gt; when design and performance are genuinely holding you back but your commerce logic still fits a platform. Keep the money-plumbing managed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go fully custom&lt;/strong&gt; only when your business model itself doesn't fit any platform — and the revenue or reclaimed margin clearly justifies owning the whole stack, maintenance included.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you do move off Shopify, migrate in stages rather than in one high-stakes cutover. Go headless first — keep the commerce engine, replace only the front end — and prove the new storefront converts before you touch the money-plumbing. Preserve your URL structure and 301-redirect anything that changes, or you'll hand back months of hard-won SEO the day you launch. Keep the old store live until the new one is measurably at least as good on conversion and page speed. A replatform that tanks organic traffic or checkout conversion can erase years of margin gains overnight, so treat the migration itself as a first-class project with its own budget and rollback plan, not an afterthought to the build.&lt;/p&gt;

&lt;p&gt;Whichever direction you lean, don't forget the fundamentals that move the number that matters: our &lt;a href="https://dev.to/blog/ecommerce-conversion-optimization"&gt;e-commerce conversion optimization&lt;/a&gt; guide will usually find more revenue than a replatform will, and it's a fraction of the cost.&lt;/p&gt;

&lt;p&gt;At Doktouri we've shipped both Shopify storefronts and fully custom commerce platforms, so we have no incentive to push you toward the more expensive answer — we can tell you which one your business actually needs. If you're weighing the move, &lt;a href="https://dev.to/#contact"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/custom-ecommerce-vs-shopify" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>shopify</category>
      <category>custom</category>
      <category>strategy</category>
    </item>
    <item>
      <title>A developer's guide to Core Web Vitals</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 22:49:37 +0000</pubDate>
      <link>https://dev.to/agenyc/a-developers-guide-to-core-web-vitals-1kd5</link>
      <guid>https://dev.to/agenyc/a-developers-guide-to-core-web-vitals-1kd5</guid>
      <description>&lt;p&gt;Core Web Vitals are Google's attempt to quantify what "fast" feels like, and they feed into search ranking. There are three: &lt;strong&gt;LCP&lt;/strong&gt; measures loading, &lt;strong&gt;INP&lt;/strong&gt; measures responsiveness, and &lt;strong&gt;CLS&lt;/strong&gt; measures visual stability. Passing them isn't about a single trick — it's about understanding what each one measures and fixing the specific things that break it.&lt;/p&gt;

&lt;h2&gt;
  
  
  LCP — Largest Contentful Paint
&lt;/h2&gt;

&lt;p&gt;LCP is the time until the biggest element in the viewport (usually a hero image or headline block) finishes rendering. Aim for &lt;strong&gt;under 2.5 seconds&lt;/strong&gt;. Common causes of a bad LCP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A large, unoptimized hero image&lt;/li&gt;
&lt;li&gt;Render-blocking CSS or JavaScript in the &lt;code&gt;&amp;lt;head&amp;gt;\&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Slow server response (high TTFB)&lt;/li&gt;
&lt;li&gt;Web fonts that delay text rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fixes: serve modern image formats (WebP/AVIF) at the right size, preload the LCP image, inline critical CSS, and render HTML on the server so the browser has something to paint immediately. If your TTFB is high, the problem is upstream — caching or hosting, not the front end.&lt;/p&gt;

&lt;h2&gt;
  
  
  INP — Interaction to Next Paint
&lt;/h2&gt;

&lt;p&gt;INP replaced First Input Delay and is stricter. It measures the latency of interactions across the whole page visit — click a button, how long until the screen visibly responds? Aim for &lt;strong&gt;under 200 milliseconds&lt;/strong&gt;. Bad INP almost always means the main thread is blocked by JavaScript.&lt;/p&gt;

&lt;p&gt;Fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Break up long tasks so the browser can respond between them&lt;/li&gt;
&lt;li&gt;Ship less JavaScript — code-split and lazy-load what isn't needed on first load&lt;/li&gt;
&lt;li&gt;Defer non-critical work with &lt;code&gt;requestIdleCallback\&lt;/code&gt; or by yielding to the main thread&lt;/li&gt;
&lt;li&gt;Avoid heavy synchronous work in event handlers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;INP is the metric most sites fail after the recent shift, and it's usually a symptom of too much client-side JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLS — Cumulative Layout Shift
&lt;/h2&gt;

&lt;p&gt;CLS measures unexpected movement — content jumping as the page loads. Aim for &lt;strong&gt;under 0.1&lt;/strong&gt;. The usual culprits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images and embeds without width and height attributes&lt;/li&gt;
&lt;li&gt;Ads or banners injected above existing content&lt;/li&gt;
&lt;li&gt;Web fonts causing a reflow when they swap in&lt;/li&gt;
&lt;li&gt;Content inserted dynamically without reserved space&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fixes: always set explicit dimensions or an aspect-ratio on media, reserve space for anything that loads late, and preload fonts with &lt;code&gt;font-display: optional\&lt;/code&gt; or &lt;code&gt;swap\&lt;/code&gt; chosen deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure with the right tools
&lt;/h2&gt;

&lt;p&gt;There's a critical distinction: &lt;strong&gt;field data&lt;/strong&gt; (real users, in Google's CrUX report) is what actually affects ranking, while &lt;strong&gt;lab data&lt;/strong&gt; (Lighthouse, one synthetic run) is just a diagnostic. A green Lighthouse score means nothing if real users on slow phones are failing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;strong&gt;PageSpeed Insights&lt;/strong&gt; to see both field and lab data for a URL.&lt;/li&gt;
&lt;li&gt;Use the &lt;strong&gt;web-vitals&lt;/strong&gt; JavaScript library to collect real INP and LCP from your own users.&lt;/li&gt;
&lt;li&gt;Watch &lt;strong&gt;Search Console's&lt;/strong&gt; Core Web Vitals report for trends across your whole site.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The order of operations
&lt;/h2&gt;

&lt;p&gt;Fix LCP first (it's usually the biggest win and touches SSR and images), then INP (reduce JavaScript), then CLS (reserve layout space). Treat it as ongoing hygiene, not a one-time audit — a single heavy third-party script can undo months of work.&lt;/p&gt;

&lt;p&gt;If your Search Console is flagging poor vitals and you want them green without a rewrite, &lt;a href="https://dev.to/#contact"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/core-web-vitals-guide" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>web</category>
      <category>performance</category>
      <category>corewebvitals</category>
      <category>seo</category>
    </item>
    <item>
      <title>Setting up a CI/CD pipeline</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 19:46:52 +0000</pubDate>
      <link>https://dev.to/agenyc/setting-up-a-cicd-pipeline-523</link>
      <guid>https://dev.to/agenyc/setting-up-a-cicd-pipeline-523</guid>
      <description>&lt;p&gt;The difference between a team that ships daily and one that dreads every release usually isn't talent — it's the pipeline. A good CI/CD setup turns deployment from a nerve-wracking event into a non-event that happens dozens of times a day. The goal is simple: every commit is automatically proven safe and can reach production without a human babysitting it. Here's how we build that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous integration: catch problems before merge
&lt;/h2&gt;

&lt;p&gt;CI runs on every push and pull request, and its job is to fail loudly and early. A solid pipeline runs these stages, fastest first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lint and format&lt;/strong&gt; checks — cheap, instant feedback on style and obvious mistakes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type checks&lt;/strong&gt; — with &lt;strong&gt;TypeScript&lt;/strong&gt;, this catches a whole class of bugs before any test runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit and integration tests&lt;/strong&gt; — the core safety net; keep them fast so people don't route around them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt; — prove the app actually compiles and packages into a deployable artifact.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Order matters. Run the quick checks first so a lint failure doesn't wait ten minutes behind the test suite. Keep the whole run under about ten minutes, or developers will start ignoring it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build once, promote the same artifact
&lt;/h2&gt;

&lt;p&gt;A common mistake is rebuilding the application separately for staging and production. Don't. &lt;strong&gt;Build a single artifact&lt;/strong&gt; — a &lt;strong&gt;Docker&lt;/strong&gt; image is ideal — and promote that exact image through environments. If it passed tests in staging, the identical bytes go to production. This eliminates the "it worked in staging" class of bug entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous delivery: automate the boring, gate the scary
&lt;/h2&gt;

&lt;p&gt;For most teams, merging to the main branch should automatically deploy to staging. Production can be one click behind that, or fully automated once you trust your tests. The key is that deployment is a script, not a runbook a person follows by hand at midnight.&lt;/p&gt;

&lt;p&gt;Use a &lt;strong&gt;safe rollout strategy&lt;/strong&gt; so a bad release doesn't take everyone down at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blue-green&lt;/strong&gt; deployments keep the old version warm so you can switch back instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canary&lt;/strong&gt; releases send a small slice of traffic to the new version first and watch the metrics.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Make rollback a first-class button
&lt;/h2&gt;

&lt;p&gt;Everything fails eventually. What separates calm teams from panicking ones is how fast they recover. Your rollback should be a single, well-tested command that reverts to the last known-good version in seconds. If rolling back is scary or slow, you'll hesitate exactly when speed matters most. Rehearse it before you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guard the secrets and the branch
&lt;/h2&gt;

&lt;p&gt;Never bake secrets into images or logs — inject them at runtime from a secrets manager. Protect your main branch: require passing checks and review before merge, so the pipeline is the only path to production and no one can push around it. These two rules prevent most self-inflicted outages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start small and grow
&lt;/h2&gt;

&lt;p&gt;You don't need the full setup on day one. Begin with automated tests on every PR and one-command deploys. Add canary rollouts and richer monitoring as the stakes rise. Even a minimal pipeline — tests plus automated deploy — transforms how a team works, because it turns "did I break something?" from a fear into a fact the machine answers for you.&lt;/p&gt;

&lt;p&gt;If your releases still involve held breath and manual steps, &lt;a href="https://dev.to/#contact"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/ci-cd-pipeline-setup" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>cicd</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to choose a tech stack for your startup</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 19:46:18 +0000</pubDate>
      <link>https://dev.to/agenyc/how-to-choose-a-tech-stack-for-your-startup-4312</link>
      <guid>https://dev.to/agenyc/how-to-choose-a-tech-stack-for-your-startup-4312</guid>
      <description>&lt;p&gt;Choosing a tech stack is one of the few early decisions you'll live with for years, yet founders often make it by copying whatever a popular blog post recommended last month. The right stack isn't the newest one — it's the one your team can ship in, hire for, and still be productive with after the honeymoon ends. A stack is a set of bets about your future: how fast you'll grow, who you'll hire, what your product becomes. Get the bets roughly right and the code almost writes itself. Get them wrong and you spend your second year fighting the decisions of your first. Here's the framework we actually use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the team you have
&lt;/h2&gt;

&lt;p&gt;The single biggest predictor of velocity is whether your engineers already know the stack. A senior team that's fast in &lt;strong&gt;TypeScript&lt;/strong&gt; and &lt;strong&gt;PostgreSQL&lt;/strong&gt; will out-ship a team fumbling through an unfamiliar "better" language every time. Familiarity compounds: it's not just syntax, it's knowing the debugger, the testing patterns, the deployment quirks, the libraries that are actually good versus the ones with great READMEs and abandoned issue trackers. Optimize for the tools your people are dangerous in today, not the ones you'd pick in a vacuum.&lt;/p&gt;

&lt;p&gt;If you're hiring soon, factor in the labor market. A stack with a deep talent pool — TypeScript, Python, React — means you can staff up quickly and cover for a departure without a crisis. An exotic choice (Elixir, Rust on the web, a niche framework) narrows your funnel to a handful of specialists who cost more, take longer to find, and are harder to replace when one leaves. That's a fine trade if the technology is genuinely core to your product's value. It's a terrible trade if you picked it because it was interesting.&lt;/p&gt;

&lt;p&gt;There's a subtler cost too: onboarding. A new hire on a mainstream stack is productive in days. On an unusual one, weeks. Multiply that across every engineer you'll ever add and the "boring" choice quietly wins on total cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match the stack to the problem, not the trend
&lt;/h2&gt;

&lt;p&gt;Ask what your product actually needs before reaching for anything fancy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data-heavy and relational?&lt;/strong&gt; A boring, reliable &lt;strong&gt;PostgreSQL&lt;/strong&gt; database will take you further than any trendy alternative. It does JSON, full-text search, geospatial queries, and transactional integrity in one engine — see &lt;a href="https://dev.to/blog/postgresql-for-startups"&gt;PostgreSQL for startups&lt;/a&gt; for why it's the default we reach for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time or collaborative?&lt;/strong&gt; Look at &lt;strong&gt;Supabase&lt;/strong&gt; for managed real-time subscriptions, or plan a websocket layer early rather than bolting one on later. Our &lt;a href="https://dev.to/blog/realtime-features-architecture"&gt;realtime features architecture&lt;/a&gt; guide covers the patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content-driven web app?&lt;/strong&gt; Next.js with server-side rendering gives you SEO without a separate CMS. If organic traffic matters, read &lt;a href="https://dev.to/blog/server-side-rendering-seo"&gt;why server-side rendering still wins for SEO&lt;/a&gt; before you commit to a client-only SPA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard CRUD API?&lt;/strong&gt; A &lt;strong&gt;NestJS&lt;/strong&gt; service on Node keeps structure without ceremony, and it shares a language with your front end so context-switching is cheap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI or search features?&lt;/strong&gt; You'll want vector storage. Postgres with the pgvector extension covers most needs before you reach for a dedicated &lt;a href="https://dev.to/blog/vector-databases-explained"&gt;vector database&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most products are more ordinary than their founders think. The novelty lives in the product idea and the go-to-market, not in the plumbing. Ordinary problems deserve proven tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Favor boring, well-supported technology
&lt;/h2&gt;

&lt;p&gt;For an early-stage company, boring is a feature. Mature technologies have documentation, Stack Overflow answers, hardened libraries, LLM training data (your AI pair-programmer is far more useful on a popular stack), and known failure modes. Bleeding-edge tools have GitHub issues and a Discord where the maintainer promises a fix "soon."&lt;/p&gt;

&lt;p&gt;The test we apply: &lt;strong&gt;has someone already hit the problem you're about to hit, and written it down?&lt;/strong&gt; With Postgres, React, or Node, the answer is almost always yes. With a two-year-old framework, you're the QA team. Reserve your innovation budget — and every team has a limited one — for the product itself. Every unusual infrastructure choice is a tax you pay on every future hire, every debugging session, and every 2 a.m. incident when the one person who understood it is asleep or gone.&lt;/p&gt;

&lt;p&gt;This isn't an argument against ever adopting new tools. It's an argument for spending your novelty where it earns its keep. Pick one thing to be adventurous about, if you must, and make everything around it dependable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build-vs-buy default
&lt;/h2&gt;

&lt;p&gt;A modern stack is as much about what you &lt;em&gt;don't&lt;/em&gt; build. Auth, email delivery, payments, file storage, background jobs, error tracking — every one of these is a solved problem you can rent. Managed services (Supabase, Clerk, Stripe, Resend, Sentry) cost real money at scale, but at the stage where you're choosing a stack, engineering time is far more expensive than a SaaS invoice. Build the thing that's yours; buy the thing that's everyone's. You can always bring a service in-house later once it's a proven cost center — that's a good problem to have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for two years, not two months
&lt;/h2&gt;

&lt;p&gt;The trap sits on both ends. Kubernetes and microservices for a pre-launch product is premature complexity: you inherit the operational burden of a company ten times your size while serving zero users. But a stack that physically can't grow — a database that won't scale, a no-code tool you'll have to rip out at 10,000 users, a framework with a hard ceiling — is a false economy that mortgages your future for a fast start.&lt;/p&gt;

&lt;p&gt;The sweet spot is a &lt;strong&gt;modular monolith&lt;/strong&gt; on managed hosting: a single deployable app with clean internal boundaries, simple enough to move fast now, structured enough to split apart later if you genuinely need to. You almost never need to decide microservices vs monolith on day one — and if you're tempted, our &lt;a href="https://dev.to/blog/microservices-vs-monolith"&gt;microservices vs monolith&lt;/a&gt; breakdown will probably talk you out of it. Draw module lines where your domains actually separate (billing, users, the core product), keep them talking through well-defined interfaces, and you'll have the option to extract a service the day the traffic justifies it — not before.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example: the default stack we reach for
&lt;/h2&gt;

&lt;p&gt;To make this concrete, here's the stack we hand most early-stage web products, and the reasoning behind each choice — not because it's universally correct, but because it's a defensible default you'd need a specific reason to deviate from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript everywhere.&lt;/strong&gt; One language across front end, back end, and shared validation. Types catch a whole class of bugs before runtime and make refactoring safe, which matters most exactly when you're changing things fastest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js for the web front end.&lt;/strong&gt; Server rendering for SEO, a mature ecosystem, and a hosting story that's a git push. If you're weighing it against plain React, &lt;a href="https://dev.to/blog/nextjs-vs-react"&gt;Next.js vs React&lt;/a&gt; lays out when the extra framework earns its keep.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL for data.&lt;/strong&gt; Relational integrity, JSON when you need flexibility, and it scales further than most startups ever test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase or a NestJS API for the backend.&lt;/strong&gt; Managed auth, storage, and real-time out of the box, or a structured Node API when you need more control. Compare the managed options in &lt;a href="https://dev.to/blog/supabase-vs-firebase"&gt;Supabase vs Firebase&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed hosting, one deployable app.&lt;/strong&gt; No Kubernetes, no service mesh, no ops team you don't have.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This stack has a deep talent pool, excellent documentation, strong AI-assistant support, and a clear upgrade path. That combination — not any single tool's cleverness — is what makes it a good bet. Deviate deliberately, for a reason you've written down, not by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes we see
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resume-driven development.&lt;/strong&gt; Choosing a technology because it's good for the engineer's career, not the product. It's an understandable instinct and a costly one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimizing for a scale you don't have.&lt;/strong&gt; Architecting for a million users while you're chasing your first hundred. The million-user rewrite is a luxury problem; ship first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fragmenting the language.&lt;/strong&gt; A Go backend, a Python data layer, and a TypeScript front end means three ecosystems, three sets of tooling, three ways to be an expert. One language across the stack is a real force multiplier for a small team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring the deployment story.&lt;/strong&gt; A stack isn't just the code — it's how it gets to production. If you can't describe how you'll deploy, monitor, and roll back on day one, you haven't finished choosing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confusing TypeScript with optional.&lt;/strong&gt; For anything non-trivial, type safety pays for itself fast. If you're on the fence, &lt;a href="https://dev.to/blog/typescript-worth-it"&gt;is TypeScript worth it&lt;/a&gt; makes the case.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Write the decision down
&lt;/h2&gt;

&lt;p&gt;Whatever you choose, record &lt;em&gt;why&lt;/em&gt; in a short document — an architecture decision record, even a single paragraph per major choice. Six months from now, someone will question a call, and "we picked Postgres because we needed transactional integrity and JSON in one database, and we were willing to trade horizontal-scale simplicity for it" is a far better answer than a shrug. Writing it down also forces you to actually have a reason, which catches the choices you were making on vibes.&lt;/p&gt;

&lt;p&gt;A stack decision you can defend is a stack decision you can revisit deliberately instead of by accident. Circumstances change — a bet that was right at ten users can be wrong at ten thousand — and the teams that adapt gracefully are the ones who know which assumptions they were betting on in the first place.&lt;/p&gt;

&lt;p&gt;We help founders make these calls without the hype every week — if you're staring at a blank architecture diagram, &lt;a href="https://dev.to/#contact"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/choosing-a-tech-stack" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>techstack</category>
      <category>startup</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Caching strategies for real apps</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 19:40:39 +0000</pubDate>
      <link>https://dev.to/agenyc/caching-strategies-for-real-apps-1if6</link>
      <guid>https://dev.to/agenyc/caching-strategies-for-real-apps-1if6</guid>
      <description>&lt;p&gt;Caching is the cheapest speed you'll ever buy — and the fastest way to ship subtle, maddening bugs if you do it carelessly. The famous joke that "there are only two hard problems in computer science: cache invalidation and naming things" exists for a reason. Done well, caching cuts latency and load dramatically. Done badly, it serves stale data to users and erodes their trust. Here's how to layer it deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache at the right layer
&lt;/h2&gt;

&lt;p&gt;A request passes through several places where you can cache, each catching a different kind of work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client / browser cache.&lt;/strong&gt; HTTP caching headers (&lt;code&gt;Cache-Control\&lt;/code&gt;, &lt;code&gt;ETag\&lt;/code&gt;) let the browser skip requests entirely for static assets. Free and enormously effective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN cache.&lt;/strong&gt; A CDN serves cached responses from the edge, close to users. Ideal for static files and cacheable API responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application cache.&lt;/strong&gt; An in-memory store like &lt;strong&gt;Redis&lt;/strong&gt; holds computed results, session data, and hot database rows so you don't recompute or refetch them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database cache.&lt;/strong&gt; Query result caching and materialized views cut repeated expensive queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best strategy usually combines several layers. A request that never reaches your server is faster and cheaper than one you serve quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick a pattern that matches your data
&lt;/h2&gt;

&lt;p&gt;For application caching, the most common and reliable pattern is &lt;strong&gt;cache-aside&lt;/strong&gt;: on a read, check the cache first; on a miss, load from the database, store it in the cache, then return it. It's simple and puts you in control of what's cached and for how long.&lt;/p&gt;

&lt;p&gt;Set a &lt;strong&gt;TTL&lt;/strong&gt; (time to live) on entries so even if invalidation logic has a gap, stale data expires on its own. A short TTL is a safety net — it bounds how wrong the cache can be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invalidation is the hard part — plan for it
&lt;/h2&gt;

&lt;p&gt;Stale data is the cost of caching, and the whole craft is managing it. A few tactics that keep you out of trouble:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prefer expiry over cleverness.&lt;/strong&gt; A short TTL is often simpler and safer than intricate invalidation logic, and it fails gracefully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invalidate on write.&lt;/strong&gt; When data changes, evict or update the relevant cache key in the same operation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Namespace and version your keys&lt;/strong&gt; so you can invalidate a whole category at once and avoid collisions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ask a blunt question for each cached value: &lt;em&gt;how wrong can this be, and for how long, before a user is harmed?&lt;/em&gt; A product catalog can be seconds stale; an account balance cannot. Let that answer set your TTLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't cache what you don't need to
&lt;/h2&gt;

&lt;p&gt;Caching adds a moving part, and moving parts break. Before adding a cache, confirm you actually have a hot path worth optimizing — measure first. Sometimes the right fix is an index or a better query, not another layer to keep in sync. Never cache highly personalized or security-sensitive data at a shared layer like a CDN, where one user could receive another's response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer up gradually
&lt;/h2&gt;

&lt;p&gt;Start with the cheap wins: proper HTTP caching headers and a CDN for static assets. Add &lt;strong&gt;Redis&lt;/strong&gt; for genuinely expensive computations and hot data as load grows. Introduce each layer only when you can point to the metric it improves. Caching should be a scalpel you apply to measured bottlenecks — not a blanket you throw over the whole app and hope.&lt;/p&gt;

&lt;p&gt;If your app is slow and you're not sure which layer to cache, &lt;a href="https://dev.to/#contact"&gt;talk to us&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/caching-strategies" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>caching</category>
      <category>performance</category>
      <category>redis</category>
    </item>
    <item>
      <title>How to build an AI chatbot that's actually useful</title>
      <dc:creator>Doktouri</dc:creator>
      <pubDate>Wed, 08 Jul 2026 19:40:05 +0000</pubDate>
      <link>https://dev.to/agenyc/how-to-build-an-ai-chatbot-thats-actually-useful-46fc</link>
      <guid>https://dev.to/agenyc/how-to-build-an-ai-chatbot-thats-actually-useful-46fc</guid>
      <description>&lt;p&gt;Wiring a language model to a chat box takes an afternoon. Building a chatbot people trust and return to takes real engineering. The gap between "it responds" and "it's useful" is where most projects stall — filled with confident wrong answers, lost context, and a bot that cheerfully promises refunds it can't issue. The demo dazzles the stakeholders; the production version has to survive real users asking real, weird questions at 2 a.m. Here's how to cross that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a job, not a technology
&lt;/h2&gt;

&lt;p&gt;Before any code, define the one job the bot exists to do: deflect support tickets, qualify leads, help users navigate a complex product, answer questions over your documentation. A bot with a job can be scoped, measured, and improved. A bot that's just "AI on our site" pleases no one and can't be evaluated because there's no definition of success. Write the job down in a sentence. Everything below serves it.&lt;/p&gt;

&lt;p&gt;That sentence also sets your bar for "good enough." A support-deflection bot succeeds if it resolves a meaningful share of tickets and hands off the rest cleanly — it does not need to be right 100% of the time, it needs to know when it isn't. A lead-qualification bot succeeds if it books qualified calls, not if it holds a charming conversation. Defining the job defines the metric, and the metric is what tells you whether to keep tuning or ship. Without it, you'll polish forever and never know if you're done. This is the same discipline that separates a real feature from a demo across AI work generally — the framing in &lt;a href="https://dev.to/blog/integrate-ai-into-your-product"&gt;how to integrate AI into your product&lt;/a&gt; applies directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ground it or it will make things up
&lt;/h2&gt;

&lt;p&gt;A chatbot answering from the model's general knowledge is a liability the moment users ask about &lt;em&gt;your&lt;/em&gt; product, pricing, or policies. It will invent plausible, confidently-wrong answers — and a confident wrong answer is worse than "I don't know," because users act on it. The fix is &lt;strong&gt;grounding&lt;/strong&gt;: retrieve relevant facts from your own content at query time and feed them into the prompt, so answers come from your data, not the model's imagination.&lt;/p&gt;

&lt;p&gt;This is retrieval-augmented generation (RAG) applied to conversation — the pattern we unpack for non-engineers in &lt;a href="https://dev.to/blog/rag-explained-for-founders"&gt;RAG explained for founders&lt;/a&gt;. The mechanics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Chunk your knowledge&lt;/strong&gt; — docs, help center, policies — into passages of a few hundred tokens, split on meaningful boundaries (sections, not arbitrary character counts).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embed each chunk&lt;/strong&gt; into a vector and store it. &lt;strong&gt;pgvector&lt;/strong&gt; on &lt;strong&gt;PostgreSQL&lt;/strong&gt; works well to start and keeps everything in one database; graduate to a dedicated &lt;a href="https://dev.to/blog/vector-databases-explained"&gt;vector database&lt;/a&gt; only when scale demands it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieve&lt;/strong&gt; the most relevant chunks for each incoming question via similarity search, optionally reranked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instruct the model&lt;/strong&gt; to answer &lt;em&gt;only&lt;/em&gt; from the retrieved passages, cite them, and explicitly say when the answer isn't in the provided context.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Retrieval quality is where most RAG bots live or die. If the right chunk never gets retrieved, the best prompt in the world can't save the answer. Invest here: tune chunk size, test your retrieval on real questions, and consider hybrid keyword-plus-vector search for queries full of exact product names or SKUs that pure semantic search fumbles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manage context deliberately
&lt;/h2&gt;

&lt;p&gt;Conversations have memory, and memory has limits — both a token budget and an attention budget. You can't stuff an entire chat history into every request: it gets expensive, it's slow, and past a point the model loses focus on what matters. Handle context on purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep recent turns verbatim&lt;/strong&gt; so the immediate thread stays coherent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summarize older history&lt;/strong&gt; into a compact running summary rather than dropping it entirely, so the bot remembers that the user is on the Pro plan and already tried restarting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-retrieve per turn.&lt;/strong&gt; Fetch fresh grounding for the &lt;em&gt;current&lt;/em&gt; question, not just the first one — conversations drift, and stale context produces answers to a question the user already moved past.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Put guardrails around it
&lt;/h2&gt;

&lt;p&gt;A production bot needs boundaries, both to stay safe and to stay on-topic:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scope it.&lt;/strong&gt; A system prompt that defines what the bot does — and refuses politely outside it — prevents your support bot from writing poetry, giving medical advice, or being talked into your competitor's marketing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate actions.&lt;/strong&gt; If the bot can &lt;em&gt;do&lt;/em&gt; things (create a ticket, issue a refund, change a subscription), gate those behind explicit confirmation and server-side authorization checks. Never let raw model output directly trigger a sensitive operation. Treat the model as an untrusted user that proposes actions your backend independently verifies against the real user's permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defend against prompt injection.&lt;/strong&gt; Retrieved content and user messages can contain instructions that try to hijack the bot ("ignore previous instructions and..."). Keep system instructions and untrusted content clearly separated, never grant the model more authority than the user it's acting for, and screen inputs and outputs. This overlaps with ordinary application security — the basics in &lt;a href="https://dev.to/blog/secure-your-web-app"&gt;how to secure your web app&lt;/a&gt; still apply.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Choose the model deliberately — and keep the seam
&lt;/h2&gt;

&lt;p&gt;Bigger isn't always better. Use a capable model for the hard reasoning turns, but route simple classification or routing steps to a smaller, cheaper, faster one. A frontier model on every keystroke will make the unit economics ugly at scale, and latency is part of the product — a bot that takes eight seconds to reply feels broken no matter how good the answer. Streaming responses token-by-token helps perceived speed enormously.&lt;/p&gt;

&lt;p&gt;Because models and prices change every few months, wrap the whole thing in a thin, typed &lt;strong&gt;TypeScript&lt;/strong&gt; service with a clean model-agnostic interface. That seam lets you swap providers, tune prompts, and adjust retrieval without touching the front end — and lets you A/B a cheaper model against your current one. On managing that bill as usage grows, see &lt;a href="https://dev.to/blog/llm-cost-optimization"&gt;LLM cost optimization&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide: chatbot or workflow?
&lt;/h2&gt;

&lt;p&gt;Not every "AI feature" should be a free-form chat. If the task is a fixed sequence — collect three fields, look something up, take one action — a structured &lt;a href="https://dev.to/blog/ai-agents-vs-workflows"&gt;agent or workflow&lt;/a&gt; is more reliable, cheaper, and easier to test than an open conversation. Reach for a chatbot when the interaction is genuinely open-ended. Using a chat interface for what's really a form frustrates users and multiplies your failure modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reference architecture
&lt;/h2&gt;

&lt;p&gt;Pulling it together, a production chatbot is more moving parts than the chat box suggests. A workable shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion pipeline&lt;/strong&gt; — a job that chunks your source content, embeds it, and upserts it into the vector store, re-running whenever the source changes so the bot never answers from stale docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval layer&lt;/strong&gt; — takes the user's message plus recent context, runs the similarity (and ideally keyword) search, and returns the top passages with their source links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration service&lt;/strong&gt; — the typed TypeScript seam that assembles the prompt (system instructions + retrieved context + trimmed history), calls the model, streams the response, and enforces the guardrails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action layer&lt;/strong&gt; — a small, explicit set of server-side functions the model may &lt;em&gt;propose&lt;/em&gt; calling, each re-checking authorization before it runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; — logging, the evaluation test set, and the quality signal wired in from day one, not bolted on after launch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these is exotic, but skipping any one is how a demo fails to become a product. The retrieval and observability layers are the ones teams most often shortcut, and the ones that most determine whether the bot is actually trusted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design the escape hatch
&lt;/h2&gt;

&lt;p&gt;The single most trust-building feature of any chatbot is a graceful handoff. When the bot is unsure, when it's failed twice, or when the user is clearly frustrated, it should offer a human, a form, or a clear next step — not loop endlessly apologizing. A bot that knows its limits and hands off cleanly feels far more competent than one that fakes confidence to the bitter end. Detect frustration and repeated failure explicitly, and make the exit obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the evaluation loop
&lt;/h2&gt;

&lt;p&gt;You cannot improve a chatbot by vibes, and "it seemed fine when I tried it" is how bad bots ship. Set up a habit of measurement before you launch, not after:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A test set&lt;/strong&gt; of real questions with known-good answers, run automatically on every prompt, model, or retrieval change so you catch regressions before users do. This is the closest thing RAG has to unit tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging&lt;/strong&gt; of real conversations (respecting privacy and consent) so you see where it actually fails in the wild — the failures you didn't imagine are the ones that matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A quality signal&lt;/strong&gt; — thumbs up/down, a resolution flag, or whether the conversation ended in a handoff — so you can track quality as a number over time rather than a feeling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then close the loop: mine the failures, fix the retrieval gaps or prompt holes they reveal, and re-run the test set. That iteration cadence, not any single clever prompt, is what turns a mediocre bot into a good one.&lt;/p&gt;

&lt;p&gt;A useful chatbot is scoped to a real job, grounded in your data, careful with context, guarded against misuse, honest about uncertainty, and continuously evaluated. Skip those and you have a demo that impresses in the meeting and disappoints in production. If you want to build one that actually earns its place in your product, &lt;a href="https://dev.to/#contact"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://agency.doktouri.com/blog/build-an-ai-chatbot" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://agency.doktouri.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>chatbot</category>
      <category>llm</category>
      <category>product</category>
    </item>
  </channel>
</rss>
