<?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: Pykero</title>
    <description>The latest articles on DEV Community by Pykero (@pykero).</description>
    <link>https://dev.to/pykero</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: Pykero</title>
      <link>https://dev.to/pykero</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pykero"/>
    <language>en</language>
    <item>
      <title>When to Ask for Push Notification Permission (And When Not To)</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 09:31:57 +0000</pubDate>
      <link>https://dev.to/pykero/when-to-ask-for-push-notification-permission-and-when-not-to-42a3</link>
      <guid>https://dev.to/pykero/when-to-ask-for-push-notification-permission-and-when-not-to-42a3</guid>
      <description>&lt;p&gt;Most apps ask for push notification permission on the first screen the user sees, before they've done anything worth being notified about. On iOS, that single dialog is a one-shot deal — decline it and the only way back is a trip to Settings that almost nobody makes. We've watched opt-in rates on client apps swing from under 30% to over 70% by changing nothing except &lt;em&gt;when&lt;/em&gt; the OS prompt fires. The copy, the icon, the app itself stayed the same.&lt;/p&gt;

&lt;p&gt;If you're building or rebuilding a mobile app's onboarding flow, permission timing deserves as much design attention as the signup form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the native prompt is a one-shot resource
&lt;/h2&gt;

&lt;p&gt;iOS shows the system permission dialog exactly once per app install. There's no "ask again later" — if the user taps &lt;strong&gt;Don't Allow&lt;/strong&gt;, your only path back is &lt;code&gt;Linking.openSettings()\&lt;/code&gt; and a user willing to dig through iOS Settings &amp;gt; Notifications &amp;gt; Your App. Android has historically been more forgiving (pre-13, permission was implicit; 13+ requires an explicit runtime request, but re-prompting is still possible under some conditions), which means teams that build permission flows against Android behavior first often get burned when they ship to iOS.&lt;/p&gt;

&lt;p&gt;Treat the native prompt as a resource you spend once. Everything before it should exist to make that one shot count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The soft-ask pattern
&lt;/h2&gt;

&lt;p&gt;The fix is a &lt;strong&gt;pre-permission screen&lt;/strong&gt; you control entirely — a normal in-app UI element, not a system dialog — that explains the value of notifications before the OS prompt appears:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Get notified when your order ships" with a bell icon and two buttons, &lt;strong&gt;Enable Notifications&lt;/strong&gt; and &lt;strong&gt;Not Now&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Only tapping &lt;strong&gt;Enable Notifications&lt;/strong&gt; triggers the real OS permission request&lt;/li&gt;
&lt;li&gt;Tapping &lt;strong&gt;Not Now&lt;/strong&gt; dismisses without touching the OS-level state at all, so the real prompt is still available later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because a soft-ask rejection costs nothing — you can show it again next session, after a different action, with different copy. A hard-ask (OS) rejection on iOS is close to permanent.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;`tsx&lt;br&gt;
import * as Notifications from 'expo-notifications';&lt;/p&gt;

&lt;p&gt;async function requestPushPermission() {&lt;br&gt;
  const { status: existing } = await Notifications.getPermissionsAsync();&lt;br&gt;
  if (existing === 'granted') return true;&lt;/p&gt;

&lt;p&gt;const { status } = await Notifications.requestPermissionsAsync();&lt;br&gt;
  return status === 'granted';&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Only call requestPushPermission() after the user taps&lt;br&gt;
// "Enable Notifications" on your own in-app screen.&lt;br&gt;
`&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Timing: contextual beats chronological
&lt;/h2&gt;

&lt;p&gt;"Day 3" or "after onboarding" is chronological timing — it's easy to implement and it's why most apps still ask at launch, on a fixed step. Contextual timing ties the ask to a moment where the value is obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E-commerce&lt;/strong&gt;: after the first order is placed, not before ("track your delivery")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SaaS/dashboard apps&lt;/strong&gt;: after the user creates the first thing worth monitoring (a project, an alert, a report)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Social/messaging&lt;/strong&gt;: after the first message is sent or received, not on account creation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Booking/scheduling&lt;/strong&gt;: right after a booking is confirmed ("we'll remind you before it starts")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your app doesn't have an obvious "moment," don't fabricate one — a generic ask on session 2 or 3 still outperforms session 1, because the user has at least formed a first impression of the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform differences that break naive implementations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;iOS&lt;/strong&gt;: one shot per install (until the user manually deletes and reinstalls the app, which resets permission state). Provisional authorization (&lt;code&gt;provisional: true\&lt;/code&gt; in the request options) delivers notifications quietly to Notification Center without a prompt at all — useful for low-stakes, high-volume notification types where you'd rather earn trust than ask upfront.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android 13+&lt;/strong&gt;: &lt;code&gt;POST_NOTIFICATIONS\&lt;/code&gt; is a runtime permission like camera or location, requested the same way. Below Android 13, notifications are granted by default at install, so testing only on newer Android devices can hide bugs that show up for a real chunk of your Android install base.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background/foreground state&lt;/strong&gt;: some permission APIs behave differently depending on whether the request happens immediately on mount vs. after a &lt;code&gt;useEffect\&lt;/code&gt; delay — test the actual timing you'll ship, not just the API call in isolation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the areas where &lt;a href="https://dev.to/blog/react-native-vs-flutter"&gt;React Native vs. Flutter&lt;/a&gt; matters less than people expect — both wrap the same underlying OS permission model, and both will burn you the same way if you ask too early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrument it before you optimize it
&lt;/h2&gt;

&lt;p&gt;You cannot tune permission timing without data. Track, at minimum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Soft-ask shown → Soft-ask accepted (your in-app funnel)&lt;/li&gt;
&lt;li&gt;Soft-ask accepted → OS permission granted (how much the OS prompt itself costs you)&lt;/li&gt;
&lt;li&gt;Time-to-first-notification-received (a proxy for whether the notification is actually useful once granted)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without these events split out, "opt-in rate" as a single number hides whether the drop-off is your copy, your timing, or the OS prompt itself — three very different fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Never trigger the OS permission dialog on first launch or from a generic onboarding step&lt;/li&gt;
&lt;li&gt;Build a soft-ask screen you fully control, with copy tied to a concrete benefit&lt;/li&gt;
&lt;li&gt;Trigger the soft-ask after a contextual action, not a fixed day/step count&lt;/li&gt;
&lt;li&gt;Handle iOS's one-shot behavior explicitly — treat rejection as (near) final, and offer a Settings deep link post-decline instead of re-asking&lt;/li&gt;
&lt;li&gt;Test on Android 13+ devices specifically; don't assume pre-13 behavior generalizes&lt;/li&gt;
&lt;li&gt;Instrument each funnel step separately before changing anything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This kind of detail is easy to skip when you're racing toward a launch date, but it's cheap to build correctly the first time and expensive to retrofit once your install base has already seen — and declined — the wrong prompt. If you're scoping a new mobile build or auditing an existing one's &lt;a href="https://dev.to/blog/mobile-app-performance"&gt;performance&lt;/a&gt; and onboarding, &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://pykero.com/blog/push-notification-permission-timing" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ux</category>
      <category>reactnative</category>
      <category>notifications</category>
    </item>
    <item>
      <title>How to work with a software agency</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:30:23 +0000</pubDate>
      <link>https://dev.to/pykero/how-to-work-with-a-software-agency-n4p</link>
      <guid>https://dev.to/pykero/how-to-work-with-a-software-agency-n4p</guid>
      <description>&lt;p&gt;A good software agency can move faster than most companies could hire for, but the outcome depends as much on how you work with them as on how good they are. The same team produces a great product for one client and a frustrating one for another. The difference is usually the client's side of the relationship: the brief, the cadence, and how decisions get made. Here's how to be the client that gets great work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Brief the problem, not the solution
&lt;/h2&gt;

&lt;p&gt;The most useful thing you can hand an agency is a clear problem, the users it affects, and the outcome you need — not a pre-baked feature list. A good team will pressure-test your assumptions and often find a simpler path to the same goal. If you hand over only a spec to implement, you've bought hands, not expertise, and left the best part on the table.&lt;/p&gt;

&lt;p&gt;Bring context: who the users are, what success looks like, what constraints are non-negotiable, and what you've already tried.&lt;/p&gt;

&lt;h2&gt;
  
  
  Name one decision-maker
&lt;/h2&gt;

&lt;p&gt;Projects stall when the agency can't get a clear answer. Designate a single person on your side who can make decisions and unblock questions quickly. Feedback by committee — five stakeholders with contradictory notes — is one of the surest ways to slow a project and blow a budget.&lt;/p&gt;

&lt;p&gt;That person doesn't need to know how to code. They need to be reachable and able to decide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agree the cadence up front
&lt;/h2&gt;

&lt;p&gt;Set the rhythm before the work starts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A regular check-in — weekly is a sane default — with working software, not slides.&lt;/li&gt;
&lt;li&gt;A shared channel for quick questions so small blockers don't wait days.&lt;/li&gt;
&lt;li&gt;A visible backlog or board so you always know what's in progress and what's next.&lt;/li&gt;
&lt;li&gt;A clear escalation path for when something's off track.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Steady cadence beats long silences punctuated by big reveals. You want to catch a wrong turn in week two, not month two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give feedback on outcomes
&lt;/h2&gt;

&lt;p&gt;The most useful feedback describes the problem, not the fix. "Users won't understand this step" is far more actionable than "make the button green." Trust the team on implementation while holding them firmly to outcomes — that's the division of labor that actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expect trade-offs, not magic
&lt;/h2&gt;

&lt;p&gt;Scope, time, and cost trade against each other; no team escapes that. A partner worth keeping will tell you when something will take longer or cost more, and offer options. Treat that honesty as a feature. The agency that says yes to everything without flinching is the one to worry about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Red flags to watch for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No questions about your users or business — just eagerness to start coding.&lt;/li&gt;
&lt;li&gt;Estimates with no ranges and no assumptions stated.&lt;/li&gt;
&lt;li&gt;Reluctance to show work until it's "done."&lt;/li&gt;
&lt;li&gt;Communication that goes quiet for stretches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any one of these early is worth raising before it compounds.&lt;/p&gt;

&lt;p&gt;At Doktouri we work as a partner, not a vending machine — we question the brief, ship on a steady cadence, and tell you the trade-offs straight. If you're looking for an engineering partner, &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://pykero.com/blog/working-with-a-dev-agency" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://pykero.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>product</category>
      <category>agency</category>
      <category>process</category>
      <category>collaboration</category>
    </item>
    <item>
      <title>Agency vs in-house team</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:29:49 +0000</pubDate>
      <link>https://dev.to/pykero/agency-vs-in-house-team-1pmh</link>
      <guid>https://dev.to/pykero/agency-vs-in-house-team-1pmh</guid>
      <description>&lt;p&gt;"Should we hire an agency or build a team?" is really four questions wearing one coat: how fast do you need to move, how much will it cost, how much control do you want, and where should the knowledge live? The honest answer is that it changes with your stage — and often the smartest move is a mix. Here's how to reason about it instead of defaulting to whichever feels safer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed: the agency's clearest edge
&lt;/h2&gt;

&lt;p&gt;An established agency can start next week with a team that has already worked together. Hiring in-house — sourcing, interviewing, onboarding, waiting out notice periods — realistically takes months before anyone ships. When time to market is the binding constraint, that gap is decisive. Agencies win on speed almost every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost: cheaper up front, different long-term
&lt;/h2&gt;

&lt;p&gt;An agency's hourly or project rate looks expensive next to a salary. But a salary is the smallest part of an employee's true cost — benefits, equipment, management, and the risk of a bad hire all stack on top, and you pay whether or not there's work to do.&lt;/p&gt;

&lt;p&gt;For bursty or finite work, an agency is usually cheaper because you pay only for what you need. For steady, indefinite work, an in-house team eventually becomes the better economics. The crossover is roughly whether the work is a project or a permanent function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control and context
&lt;/h2&gt;

&lt;p&gt;In-house teams live inside your business. They absorb context in hallway conversations, feel the mission, and are always available for the quick question. An agency, however embedded, is one step removed and shared with other clients.&lt;/p&gt;

&lt;p&gt;If the work demands deep, constantly-evolving domain knowledge, in-house has a real edge. If it's a well-defined build, that gap matters far less than it feels like it should.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowledge retention: the real long-term risk
&lt;/h2&gt;

&lt;p&gt;The strongest argument for in-house is that the knowledge stays. When an agency finishes, its understanding of your system can walk out with it. Mitigate that regardless of who builds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Insist on documentation as a deliverable, not a favor.&lt;/li&gt;
&lt;li&gt;Own your repositories, infrastructure, and accounts from day one.&lt;/li&gt;
&lt;li&gt;Have your people involved enough to maintain what's built.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An agency that resists any of these is a warning sign.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match the model to the stage
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Early / pre-product-market-fit&lt;/strong&gt; — an agency gets you to a testable product fast, before you know which skills to hire permanently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling a validated product&lt;/strong&gt; — start building in-house for the core, where retained knowledge compounds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spikes and specialties&lt;/strong&gt; — use an agency for finite projects or skills you don't need full-time, even with a strong internal team.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The hybrid most teams land on
&lt;/h2&gt;

&lt;p&gt;In practice the answer is rarely pure. A common, effective pattern: a small in-house core owns the product and domain, an agency provides velocity and specialized skills, and ownership of code and infrastructure stays firmly with you. You get speed without hollowing out your own capability.&lt;/p&gt;

&lt;p&gt;At Doktouri we work both as a standalone team and as an extension of an in-house one, and we'll tell you honestly which your stage calls for. If you're weighing the decision, &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://pykero.com/blog/when-to-hire-an-agency-vs-in-house" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://pykero.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>product</category>
      <category>hiring</category>
      <category>agency</category>
      <category>strategy</category>
    </item>
    <item>
      <title>Web accessibility basics every team should ship</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:24:09 +0000</pubDate>
      <link>https://dev.to/pykero/web-accessibility-basics-every-team-should-ship-1nj4</link>
      <guid>https://dev.to/pykero/web-accessibility-basics-every-team-should-ship-1nj4</guid>
      <description>&lt;p&gt;Accessibility gets treated as a specialist compliance topic, which scares teams into doing nothing. The truth is that a handful of fundamentals cover the vast majority of real-world needs, they're cheap to build in from the start, and they make the product better for everyone — not just users with disabilities. Here's the baseline every team should ship, in priority order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with semantic HTML
&lt;/h2&gt;

&lt;p&gt;The single highest-leverage accessibility decision is using the right HTML element for the job. A &lt;code&gt;&amp;lt;button&amp;gt;\&lt;/code&gt; is focusable, keyboard-operable, and announced correctly by screen readers for free. A &lt;code&gt;&amp;lt;div&amp;gt;\&lt;/code&gt; with a click handler is none of those things.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;&amp;lt;button&amp;gt;\&lt;/code&gt; for actions, &lt;code&gt;&amp;lt;a&amp;gt;\&lt;/code&gt; for navigation — never a styled &lt;code&gt;&amp;lt;div&amp;gt;\&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use headings (&lt;code&gt;&amp;lt;h1&amp;gt;\&lt;/code&gt;–&lt;code&gt;&amp;lt;h6&amp;gt;\&lt;/code&gt;) in order to convey structure&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;&amp;lt;nav&amp;gt;\&lt;/code&gt;, &lt;code&gt;&amp;lt;main&amp;gt;\&lt;/code&gt;, &lt;code&gt;&amp;lt;header&amp;gt;\&lt;/code&gt;, and &lt;code&gt;&amp;lt;footer&amp;gt;\&lt;/code&gt; landmarks&lt;/li&gt;
&lt;li&gt;Use real &lt;code&gt;&amp;lt;label&amp;gt;\&lt;/code&gt; elements tied to their inputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Get this right and you've solved most accessibility problems before writing a line of ARIA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make everything keyboard-operable
&lt;/h2&gt;

&lt;p&gt;Many users navigate entirely by keyboard. Tab through your whole app and check two things: can you reach and activate every interactive element, and can you always &lt;strong&gt;see where focus is&lt;/strong&gt;? A visible focus ring is not optional — never remove the outline without replacing it with something clearer.&lt;/p&gt;

&lt;p&gt;Watch for keyboard traps (focus that gets stuck in a modal), custom dropdowns that ignore arrow keys, and click handlers on non-focusable elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get contrast and text right
&lt;/h2&gt;

&lt;p&gt;Low-contrast text is the most common accessibility failure and it's trivial to fix. Aim for a contrast ratio of at least &lt;strong&gt;4.5:1&lt;/strong&gt; for body text and &lt;strong&gt;3:1&lt;/strong&gt; for large text. Don't rely on color alone to convey meaning — an error state needs an icon or text, not just red.&lt;/p&gt;

&lt;p&gt;Let users zoom and resize text without breaking the layout, and never disable pinch-to-zoom on mobile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle images and dynamic content
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Give informative images meaningful &lt;code&gt;alt\&lt;/code&gt; text; give decorative images empty &lt;code&gt;alt=""\&lt;/code&gt; so screen readers skip them&lt;/li&gt;
&lt;li&gt;Announce dynamic updates (toasts, live validation) with an &lt;code&gt;aria-live\&lt;/code&gt; region so screen reader users hear them&lt;/li&gt;
&lt;li&gt;Ensure modals trap and return focus correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use ARIA sparingly and correctly
&lt;/h2&gt;

&lt;p&gt;ARIA lets you patch accessibility onto custom widgets, but the first rule of ARIA is: don't use it if native HTML can do the job. Bad ARIA is worse than none — a wrong &lt;code&gt;role\&lt;/code&gt; actively misleads assistive technology. Reserve it for genuinely custom components (tabs, comboboxes, custom dialogs) and follow the established patterns rather than improvising.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bake it into the workflow
&lt;/h2&gt;

&lt;p&gt;Accessibility fails when it's a pre-launch audit instead of a habit. Cheap ways to keep it healthy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run an automated linter (axe, eslint-plugin-jsx-a11y) in CI to catch obvious regressions&lt;/li&gt;
&lt;li&gt;Tab through new features before merging&lt;/li&gt;
&lt;li&gt;Test occasionally with an actual screen reader (VoiceOver, NVDA)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Automated tools catch maybe half of issues, so the manual checks matter. But this baseline — semantic HTML, keyboard access, contrast, and honest ARIA — clears most of the bar and rarely costs extra time when built in from day one.&lt;/p&gt;

&lt;p&gt;If you need an existing product audited against WCAG or want accessibility built into a new build from the start, &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://pykero.com/blog/web-accessibility-basics" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://pykero.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>web</category>
      <category>a11y</category>
      <category>ux</category>
    </item>
    <item>
      <title>Vector databases explained</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:23:36 +0000</pubDate>
      <link>https://dev.to/pykero/vector-databases-explained-292h</link>
      <guid>https://dev.to/pykero/vector-databases-explained-292h</guid>
      <description>&lt;p&gt;Behind almost every "AI-powered search" and RAG feature sits the same quiet workhorse: similarity search over vectors. If you're building anything that finds relevant content by &lt;em&gt;meaning&lt;/em&gt; rather than exact keywords, you need to understand vectors — and, importantly, when you do and don't need a dedicated database for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  From text to vectors
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;embedding&lt;/strong&gt; is a list of numbers — a vector — that captures the meaning of a piece of text (or an image, or audio). An embedding model turns "cancel my subscription" and "how do I stop being billed" into vectors that sit close together, even though they share almost no words.&lt;/p&gt;

&lt;p&gt;That closeness is the whole trick. Meaning becomes geometry: similar concepts land near each other in a high-dimensional space, and "find related content" becomes "find nearby vectors."&lt;/p&gt;

&lt;h2&gt;
  
  
  How similarity search works
&lt;/h2&gt;

&lt;p&gt;Once your content is stored as vectors, retrieval is straightforward in principle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Embed the user's query into a vector.&lt;/li&gt;
&lt;li&gt;Find the stored vectors closest to it — typically by cosine similarity.&lt;/li&gt;
&lt;li&gt;Return the content those nearest vectors belong to.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The challenge is doing step 2 fast when you have millions of vectors. Comparing against every one is too slow, so vector indexes use &lt;strong&gt;approximate nearest neighbor (ANN)&lt;/strong&gt; algorithms that trade a tiny bit of accuracy for enormous speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you actually need a vector database?
&lt;/h2&gt;

&lt;p&gt;This is the question that matters, and the honest answer is: &lt;strong&gt;usually not at first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you already run &lt;strong&gt;PostgreSQL&lt;/strong&gt; — and with &lt;strong&gt;Supabase&lt;/strong&gt; you do — the &lt;strong&gt;pgvector&lt;/strong&gt; extension adds vector columns and ANN indexing right inside your existing database. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One system&lt;/strong&gt; to operate, back up, and secure, not two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Joins&lt;/strong&gt; between your vectors and your regular relational data in a single query — filter by tenant, date, or category &lt;em&gt;and&lt;/em&gt; similarity together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No extra service&lt;/strong&gt; to sync, pay for, or keep consistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most products up to millions of vectors, pgvector is not a compromise — it's the pragmatic right answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a dedicated vector database earns its place
&lt;/h2&gt;

&lt;p&gt;Graduate to a specialized vector store when you hit real limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Very large scale&lt;/strong&gt; — tens or hundreds of millions of vectors where purpose-built indexing and sharding matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High query throughput&lt;/strong&gt; that would compete with your transactional workload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced retrieval features&lt;/strong&gt; like sophisticated hybrid search or filtering that a dedicated engine handles better.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even then, keep the metadata your app relies on in Postgres; let the vector store do only what it's uniquely good at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting retrieval quality right
&lt;/h2&gt;

&lt;p&gt;The database is rarely the hard part — retrieval &lt;em&gt;quality&lt;/em&gt; is. A few things move it more than your choice of engine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Match your embedding model to your domain&lt;/strong&gt;, and use the same model for indexing and querying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combine vector search with keyword search&lt;/strong&gt; (hybrid) so exact terms like product names still land.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter before or alongside similarity&lt;/strong&gt; so results respect tenancy and permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-rank&lt;/strong&gt; the top candidates to push the best context to the front.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start simple with pgvector, measure retrieval quality on real queries, and scale the infrastructure only when the numbers demand it. If you're building AI search or RAG and want it fast and accurate without over-engineering the stack, &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://pykero.com/blog/vector-databases-explained" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://pykero.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vectordatabase</category>
      <category>embeddings</category>
      <category>search</category>
    </item>
    <item>
      <title>Is TypeScript worth it?</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:17:56 +0000</pubDate>
      <link>https://dev.to/pykero/is-typescript-worth-it-40h1</link>
      <guid>https://dev.to/pykero/is-typescript-worth-it-40h1</guid>
      <description>&lt;p&gt;Here's the straight answer: for anything beyond a throwaway script or a tiny prototype, TypeScript is worth it. The costs are real but front-loaded and shrinking; the payoffs compound as the codebase and team grow. The interesting question isn't &lt;em&gt;whether&lt;/em&gt; to use it — it's understanding exactly what you're buying and what you're paying, so you adopt it with your eyes open.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you're actually buying
&lt;/h2&gt;

&lt;p&gt;TypeScript's value isn't just "catching bugs," though it does that. The bigger wins are structural:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring confidence.&lt;/strong&gt; Rename a function or change a data shape and the compiler shows you every place that breaks. On a large codebase, this turns terrifying changes into routine ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-documenting code.&lt;/strong&gt; Types are documentation that can't go stale. A function signature tells you what goes in and comes out without reading the body.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editor superpowers.&lt;/strong&gt; Real autocomplete, inline errors, and go-to-definition that actually knows your data. This alone speeds up daily work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fewer whole classes of bugs.&lt;/strong&gt; Undefined-is-not-a-function, wrong argument order, typo'd property names — gone at compile time instead of in production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These benefits grow with codebase size and team count. They're modest on a solo weekend project and enormous on a product several engineers maintain for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you're paying
&lt;/h2&gt;

&lt;p&gt;Be honest about the costs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A learning curve&lt;/strong&gt; — generics, utility types, and gnarly library types take time to get comfortable with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some upfront friction&lt;/strong&gt; — you write type annotations and occasionally fight the compiler over code you "know" is fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build tooling&lt;/strong&gt; — a compile step and configuration, though modern tools have made this nearly frictionless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type gymnastics temptation&lt;/strong&gt; — teams sometimes over-engineer types into unreadable puzzles. Discipline matters; simple types are usually the right ones.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notably, these costs are heaviest at the start and drop sharply as fluency grows. The tax you pay in week one isn't the tax you pay in month six.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adopt it incrementally
&lt;/h2&gt;

&lt;p&gt;You don't need a big-bang rewrite. TypeScript is designed for gradual adoption:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Turn it on with loose settings and &lt;code&gt;allowJs\&lt;/code&gt;, so JavaScript and TypeScript coexist&lt;/li&gt;
&lt;li&gt;Convert files as you touch them, starting with core data models and shared utilities where types pay off most&lt;/li&gt;
&lt;li&gt;Tighten &lt;code&gt;tsconfig\&lt;/code&gt; over time — enable &lt;code&gt;strict\&lt;/code&gt; once you're ready; it's where most of the safety lives&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;unknown\&lt;/code&gt; over &lt;code&gt;any\&lt;/code&gt; at boundaries, and reserve &lt;code&gt;any\&lt;/code&gt; as a deliberate, rare escape hatch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This lets a team get value immediately without halting delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it matters most — and least
&lt;/h2&gt;

&lt;p&gt;Use TypeScript for: production apps, anything a team maintains, shared libraries, and long-lived codebases. That's almost everything real.&lt;/p&gt;

&lt;p&gt;Skip it (or don't bother) for: genuine one-off scripts, quick throwaway prototypes, and tiny snippets where the ceremony outweighs the payoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;For serious software, the question is settled — TypeScript's compounding benefits to safety, refactoring, and developer experience clearly outweigh its front-loaded costs. The teams that regret it almost always over-complicated their types; the fix is discipline, not abandonment. Adopt it gradually, keep types simple, and enable strict mode when you're ready.&lt;/p&gt;

&lt;p&gt;If you're weighing a TypeScript migration on an existing JavaScript codebase and want a low-risk incremental plan, &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://pykero.com/blog/typescript-worth-it" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://pykero.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>typescript</category>
      <category>frontend</category>
      <category>dx</category>
    </item>
    <item>
      <title>Managing technical debt without halting delivery</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:17:23 +0000</pubDate>
      <link>https://dev.to/pykero/managing-technical-debt-without-halting-delivery-4n76</link>
      <guid>https://dev.to/pykero/managing-technical-debt-without-halting-delivery-4n76</guid>
      <description>&lt;p&gt;Technical debt gets talked about like moral failure — sloppy work someone should feel bad about. That framing is wrong and it leads to bad decisions. Debt is a tool: sometimes you deliberately take a shortcut to hit a deadline or test an idea, and that's a rational trade. The problem isn't taking on debt; it's taking it on blindly and never paying interest. The goal isn't zero debt — it's &lt;em&gt;managed&lt;/em&gt; debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not all debt is equal
&lt;/h2&gt;

&lt;p&gt;The financial metaphor is genuinely useful. Some debt is a smart, low-interest loan; some is a payday loan strangling your velocity. Sort it honestly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deliberate and prudent:&lt;/strong&gt; "We'll hardcode this now and generalize it when we have a second use case." Fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deliberate and reckless:&lt;/strong&gt; "We don't have time to test any of this." Dangerous.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accidental:&lt;/strong&gt; design decisions that only looked wrong once the product evolved. Inevitable and normal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What matters is the &lt;strong&gt;interest rate&lt;/strong&gt; — how much a given piece of debt slows you down every single week. High-interest debt in a hot path is worth paying down now; low-interest debt in a stable corner nobody touches can sit forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it visible
&lt;/h2&gt;

&lt;p&gt;Debt you can't see, you can't manage. The most common failure is that it lives only in senior engineers' heads as vague dread. Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write it down.&lt;/strong&gt; File tickets for known debt with a note on the pain it causes and where.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leave signposts in code.&lt;/strong&gt; A consistent &lt;code&gt;// TODO\&lt;/code&gt; or &lt;code&gt;// DEBT\&lt;/code&gt; convention you can grep for turns tribal knowledge into a searchable list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch the signals.&lt;/strong&gt; Rising bug rates in one module, slowing delivery, or the same area breaking repeatedly all point to where debt is compounding.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Making debt legible turns anxious hallway conversations into a prioritized, decidable list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pay it down continuously, not in a big bang
&lt;/h2&gt;

&lt;p&gt;The instinct to stop all feature work for a "refactoring sprint" almost always backfires — the business resents it, and a giant rewrite is its own risk. Continuous, incremental repayment works far better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Budget a slice of every cycle&lt;/strong&gt; — say 15 to 20 percent — for debt and maintenance. Predictable and sustainable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the boy-scout rule:&lt;/strong&gt; leave code a little cleaner than you found it whenever you touch it for a feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay down debt where you're already working.&lt;/strong&gt; Refactoring the module you're about to extend is cheap; refactoring a random quiet one is speculative.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps the codebase healthy without ever fully stopping delivery, which is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tie it to business outcomes
&lt;/h2&gt;

&lt;p&gt;The reason "we need to refactor" falls on deaf ears is that it's framed as engineering hygiene. Translate it into the language stakeholders act on: &lt;em&gt;this debt is why the last three features each took a week longer than estimated,&lt;/em&gt; or &lt;em&gt;this is why we get paged every weekend.&lt;/em&gt; When you connect debt to shipped velocity, reliability, and cost, prioritizing it stops being a fight.&lt;/p&gt;

&lt;h2&gt;
  
  
  The balance
&lt;/h2&gt;

&lt;p&gt;Managing technical debt is a continuous act of judgment, not a cleanup project you finish. Take it on deliberately, track it openly, pay it down where the interest is highest, and never let it become invisible. A team that does this ships fast &lt;em&gt;and&lt;/em&gt; keeps a codebase they can still move in a year from now — you don't have to choose.&lt;/p&gt;

&lt;p&gt;If your delivery is slowing and debt is the suspect, &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://pykero.com/blog/technical-debt-management" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://pykero.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>technicaldebt</category>
      <category>refactoring</category>
      <category>process</category>
    </item>
    <item>
      <title>Supabase vs Firebase</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:11:43 +0000</pubDate>
      <link>https://dev.to/pykero/supabase-vs-firebase-3i9o</link>
      <guid>https://dev.to/pykero/supabase-vs-firebase-3i9o</guid>
      <description>&lt;p&gt;Both &lt;strong&gt;Supabase&lt;/strong&gt; and &lt;strong&gt;Firebase&lt;/strong&gt; promise the same thing: a backend you don't have to build, with auth, a database, storage, and real-time out of the box. They get you to a working app astonishingly fast, which is why they dominate the "backend-as-a-service" conversation. But under the hood they make very different bets, and the one you pick shapes how your product evolves for years. Here's how we decide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core difference: relational vs document
&lt;/h2&gt;

&lt;p&gt;Everything else follows from this. Firebase's Firestore is a NoSQL document store. Data lives in nested collections of documents, and you model it around the exact queries your screens make. That's liberating early on and painful later — anything relational (joins, aggregates, reporting, "show me all X where related Y is Z") requires denormalization, fan-out writes, and careful bookkeeping to keep duplicated data consistent. You essentially trade write simplicity and query flexibility for read speed on pre-shaped access patterns.&lt;/p&gt;

&lt;p&gt;Supabase is &lt;strong&gt;PostgreSQL&lt;/strong&gt; with a well-built platform wrapped around it. You get a real relational schema, SQL, foreign keys, joins, transactions, and every Postgres feature — JSON columns, full-text search, &lt;code&gt;pgvector\&lt;/code&gt; for AI embeddings, materialized views — from day one. For most business software, which is inherently relational, this is the more durable foundation. Our deeper take on why lives in &lt;a href="https://dev.to/blog/postgresql-for-startups"&gt;PostgreSQL for startups&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The honest version: if your data is genuinely document-shaped and query patterns are simple and known in advance, Firestore is a fine fit. The moment you need reporting, ad-hoc queries, or complex relationships — and most products eventually do — Postgres wins decisively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lock-in and portability
&lt;/h2&gt;

&lt;p&gt;This is the quiet dealbreaker for a lot of teams, and it deserves more weight than it usually gets. Firebase is proprietary and Google-hosted; there is no Firestore outside Google. Migrating off it means rewriting your entire data layer against a different database, because nothing else speaks Firestore's model or API.&lt;/p&gt;

&lt;p&gt;Supabase is open source and &lt;em&gt;just Postgres&lt;/em&gt; underneath. You can &lt;code&gt;pg_dump\&lt;/code&gt; your database and move it to any Postgres host on earth — AWS RDS, a droplet, your own hardware — self-host the entire Supabase platform, or point existing SQL tooling straight at it. Your data isn't hostage to one vendor's roadmap or pricing. If avoiding vendor lock-in matters to you at all, Supabase's answer is simply stronger, and it's not close.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time and auth
&lt;/h2&gt;

&lt;p&gt;Both handle the common cases well, with different characters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time:&lt;/strong&gt; Firestore's live listeners are mature, battle-tested, and deeply integrated — this was a first-class feature from day one. Supabase streams Postgres changes over websockets, which is excellent for typical collaborative and live-update workloads, though very high-frequency, high-fan-out cases need more thought and tuning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth:&lt;/strong&gt; Both give you email, OAuth providers, and session management within minutes. The differentiator is that Supabase pairs auth with &lt;strong&gt;row-level security&lt;/strong&gt; in Postgres, so your access rules live &lt;em&gt;in the database itself&lt;/em&gt; as declarative policies — a clean, auditable, single-source-of-truth model. Firebase uses a separate security-rules language layered on top of Firestore, which works but lives apart from your data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Offline and mobile SDKs
&lt;/h2&gt;

&lt;p&gt;One area where Firebase still leads: mature, first-party mobile SDKs with robust &lt;strong&gt;offline persistence and sync&lt;/strong&gt; built in. If you're shipping a mobile app that must work fully offline and reconcile automatically when it reconnects, Firebase's offline story is more turnkey today. Supabase's mobile support is solid and improving, but offline-first sync is something you'll assemble more deliberately. If mobile-offline is a hard requirement right now, weigh this seriously — and see &lt;a href="https://dev.to/blog/react-native-vs-flutter"&gt;React Native vs Flutter&lt;/a&gt; for the client side of that decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing and scaling behavior
&lt;/h2&gt;

&lt;p&gt;Firebase's per-operation pricing (billed per document read, write, and delete) is friendly at tiny scale but can surprise you as usage climbs — a poorly-shaped query that reads thousands of documents, or a chatty real-time screen, translates directly into a bill that's genuinely hard to predict. Supabase bills closer to compute and storage on predictable tiers, which tends to be more legible as you grow because it doesn't scale linearly with every read. Neither is universally cheaper — model your actual read/write pattern before committing, because the shape of your access dictates which one is affordable.&lt;/p&gt;

&lt;p&gt;On scaling, Firestore autoscales seamlessly and effectively infinitely, but it does so by constraining how you're &lt;em&gt;allowed&lt;/em&gt; to query — you cannot write an expensive query, because the model won't let you. Postgres gives you full query power with the classic trade-off that you'll eventually tune indexes, add read replicas, and manage connection pooling yourself (or lean on Supabase's managed layer, connection pooler, and read replicas to soften that).&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer experience and the shape of your day
&lt;/h2&gt;

&lt;p&gt;The two platforms feel different to work in, and that texture matters over months of building. With &lt;strong&gt;Supabase&lt;/strong&gt;, you're working with SQL and a relational schema, which means you can use the entire universe of Postgres knowledge, tooling, and Stack Overflow answers accumulated over decades. Migrations are versioned SQL files, the dashboard gives you a real table editor and SQL runner, and the auto-generated APIs and TypeScript types keep the client in sync with your schema. If your team knows databases, they'll feel at home immediately.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;Firebase&lt;/strong&gt;, you think in documents and collections, and you shape your data around access patterns rather than normalized truth. The tooling is polished and the SDKs are excellent, but you're learning Google's model and its security-rules language, and denormalization becomes a discipline you maintain by hand. It's fast to start and can feel magical for simple apps; the friction shows up later, when a reporting requirement or a new relationship forces you to reshape data that's already duplicated across the store.&lt;/p&gt;

&lt;h2&gt;
  
  
  A migration reality check
&lt;/h2&gt;

&lt;p&gt;Teams often ask "can I switch later?" The honest answer shapes the decision. Moving &lt;em&gt;to&lt;/em&gt; Supabase from a relational-shaped Firestore is doable but real work — you're mapping documents into tables, reconstructing relationships that were implicit, and rewriting the data layer. Moving &lt;em&gt;off&lt;/em&gt; Firebase is harder the longer you wait, because more of your app assumes its model and API. Postgres, being an open standard, is the easier thing to leave: any host, any tool, a plain &lt;code&gt;pg_dump\&lt;/code&gt; away. That asymmetry is itself an argument — starting on the more portable foundation keeps your future options open, which is exactly the lens we bring to &lt;a href="https://dev.to/blog/choosing-a-tech-stack"&gt;choosing a tech stack&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to choose Supabase
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your data is relational — which most business and SaaS data is.&lt;/li&gt;
&lt;li&gt;You need reporting, analytics, ad-hoc queries, or complex relationships.&lt;/li&gt;
&lt;li&gt;Avoiding vendor lock-in and keeping data portable matters.&lt;/li&gt;
&lt;li&gt;You want AI/vector search (&lt;code&gt;pgvector\&lt;/code&gt;) alongside your primary data.&lt;/li&gt;
&lt;li&gt;Your team already knows SQL and wants its full power.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to choose Firebase
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your data is genuinely document-shaped with simple, known query patterns.&lt;/li&gt;
&lt;li&gt;You're already deep in the Google Cloud ecosystem.&lt;/li&gt;
&lt;li&gt;You need best-in-class mobile SDKs with offline sync &lt;em&gt;today&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;You want maximum autoscaling with zero query tuning.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common mistakes teams make
&lt;/h2&gt;

&lt;p&gt;The decisions people regret tend to share a root cause — optimizing for the first week instead of the next three years:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choosing Firestore for clearly relational data&lt;/strong&gt; because it felt faster to start, then hitting a wall the moment reporting, joins, or analytics show up on the roadmap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring the pricing model until the bill arrives&lt;/strong&gt; — building read-heavy, chatty screens on Firestore's per-operation pricing and being surprised when usage scales the cost linearly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Underestimating lock-in&lt;/strong&gt; until a re-platform, an acquisition, or a compliance requirement forces a migration that a proprietary store makes genuinely painful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reaching for Supabase but never using Postgres&lt;/strong&gt; — treating it as a document store and missing the relational power, row-level security, and &lt;code&gt;pgvector\&lt;/code&gt; that were the whole point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick based on the true shape of your data and where the product is heading, not on which quickstart felt smoothest.&lt;/p&gt;

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

&lt;p&gt;Our default for SaaS is &lt;strong&gt;Supabase&lt;/strong&gt;, and the reasoning compounds: relational data, real reporting, row-level security, &lt;code&gt;pgvector\&lt;/code&gt;, and no lock-in add up to a foundation you won't outgrow. &lt;strong&gt;Firebase&lt;/strong&gt; remains a legitimate, faster on-ramp when your data is document-shaped, you live in Google's ecosystem, or you need turnkey mobile offline sync right now.&lt;/p&gt;

&lt;p&gt;If you're genuinely unsure, choose the relational option. It is far easier to ignore Postgres features you don't need yet than to bolt relationships, joins, and reporting onto a document store after the fact. This is the same principle we apply when &lt;a href="https://dev.to/blog/choosing-a-tech-stack"&gt;choosing a tech stack&lt;/a&gt; generally — pick the foundation that keeps the most doors open.&lt;/p&gt;

&lt;p&gt;One more thing worth saying plainly: the fast start is a trap if it points you at the wrong foundation. Both tools get you a working app in an afternoon, so speed-to-first-screen is a poor tiebreaker — they're roughly equal there. Judge them instead on the thing you'll actually live with, which is how your data model holds up as the product grows more complex, more relational, and more demanding of reporting. That question has a clear answer for most business software, and it's why we default to the relational side.&lt;/p&gt;

&lt;p&gt;Either way, treat the backend as a foundation you'll build on for years, not a demo shortcut. If you want help matching one to your product's real data shape, &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://pykero.com/blog/supabase-vs-firebase" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://pykero.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>supabase</category>
      <category>firebase</category>
      <category>backend</category>
    </item>
    <item>
      <title>Software project red flags to watch for</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:11:09 +0000</pubDate>
      <link>https://dev.to/pykero/software-project-red-flags-to-watch-for-5aj9</link>
      <guid>https://dev.to/pykero/software-project-red-flags-to-watch-for-5aj9</guid>
      <description>&lt;p&gt;Software projects rarely fail all at once. They fail slowly, through warning signs that were visible weeks before the missed deadline made it official. The good news is that these signals are consistent across projects — once you know them, you can spot trouble while it's still cheap to fix. Here are ten to watch for, and what to do about each.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. You haven't seen working software in weeks
&lt;/h2&gt;

&lt;p&gt;The single most reliable warning sign. If progress is only ever described in status updates and slides, you don't actually know where things stand. Insist on running software at a regular cadence. "Almost done" with nothing to click is the phrase that precedes most overruns.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Nobody can state what "done" means
&lt;/h2&gt;

&lt;p&gt;If the team and the stakeholders would describe the finished product differently, the project is built on sand. Stop and write down a shared definition of done before more code accumulates on a shaky foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Estimates have no ranges or assumptions
&lt;/h2&gt;

&lt;p&gt;A confident single-number estimate with no stated assumptions isn't precision — it's a guess in a suit. Real estimates carry ranges and list what they depend on. Their absence means the uncertainty is hidden, not gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Scope grows but the timeline doesn't
&lt;/h2&gt;

&lt;p&gt;Every project changes. The red flag is changes being absorbed silently while the deadline stays fixed. Without visible change control, you're accumulating a debt that comes due all at once near the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. One person is the only one who understands a critical part
&lt;/h2&gt;

&lt;p&gt;Key-person risk. If a single developer holds knowledge nobody else has, illness, distraction, or departure can stall the whole project. Push for documentation and shared ownership before that bus factor bites.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Communication has gone quiet
&lt;/h2&gt;

&lt;p&gt;Healthy projects are noisy — questions, demos, small course-corrections. A stretch of silence usually means someone is stuck and not saying so. Quiet is rarely a sign that everything's fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Testing is "something we'll do at the end"
&lt;/h2&gt;

&lt;p&gt;Deferring all testing to the end guarantees a crunch and hides risk until it's most expensive. Quality checks should run continuously, not pile up behind a launch date.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Every feature is "critical"
&lt;/h2&gt;

&lt;p&gt;If nothing can be cut, no one has made a real priority call. A project with no cut list has no plan for when time runs short — and time always runs short. Force a ranking.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. The team stopped asking questions
&lt;/h2&gt;

&lt;p&gt;Early on, a good team interrogates the problem. If the questions dried up, either everything is genuinely clear (rare) or people have stopped engaging and are heads-down building the wrong thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Bad news only travels upward late
&lt;/h2&gt;

&lt;p&gt;The most dangerous culture is one where problems are hidden until they're unavoidable. You want the team surfacing risks early, while there's still room to react. If you only hear about slips at the deadline, the reporting itself is broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do when you spot one
&lt;/h2&gt;

&lt;p&gt;One flag is a conversation. Several together is a pattern that needs intervention — re-establish cadence, rewrite the definition of done, and force the priority calls that got skipped. The earlier you act, the cheaper the fix.&lt;/p&gt;

&lt;p&gt;At Doktouri we run projects to keep these flags from appearing — working software on a cadence, honest estimates, and early bad news. If a project of yours is showing warning signs, &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://pykero.com/blog/software-project-red-flags" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://pykero.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>product</category>
      <category>projectmanagement</category>
      <category>risk</category>
      <category>process</category>
    </item>
    <item>
      <title>Server-side rendering and SEO</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:05:30 +0000</pubDate>
      <link>https://dev.to/pykero/server-side-rendering-and-seo-2e6c</link>
      <guid>https://dev.to/pykero/server-side-rendering-and-seo-2e6c</guid>
      <description>&lt;p&gt;Your content can be brilliant, but if a search crawler receives a blank HTML shell and a bundle of JavaScript, none of it counts. Rendering strategy — where and when your HTML gets built — quietly decides whether Google sees a finished page or an empty stage. For anything that depends on organic traffic, this is the SEO decision that comes &lt;em&gt;before&lt;/em&gt; keywords, links, or content. You can do everything else right and still be invisible if the crawler never sees your words.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three rendering strategies
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSR (client-side rendering):&lt;/strong&gt; the server sends a near-empty HTML file plus a JavaScript bundle. The browser downloads, parses, and executes that JavaScript, which then builds the page. Fast to develop and cheap to host, but the initial HTML — the thing a crawler reads first — is essentially blank.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSR (server-side rendering):&lt;/strong&gt; the server runs your application code and builds full HTML for each request, sending it ready to display. The browser paints it immediately, then "hydrates" it — attaching JavaScript to make it interactive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSG (static site generation):&lt;/strong&gt; pages are rendered to HTML once, at build time, and served as static files from a CDN. Fastest of all and nearly bulletproof to serve, but content is frozen until the next build and redeploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The distinction that matters for SEO is simple: &lt;strong&gt;SSR and SSG both hand crawlers complete HTML on the first request. CSR does not.&lt;/strong&gt; With CSR, the meaningful content only exists after JavaScript runs — and whether that happens reliably, for every crawler, on every page, is out of your hands. That single fact is the root of every problem below. Everything else in this article is a consequence of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CSR hurts SEO in practice
&lt;/h2&gt;

&lt;p&gt;Google &lt;em&gt;can&lt;/em&gt; execute JavaScript, so people assume CSR is fine. In reality it's fragile, and the fragility is the whole problem. Google's indexing happens in two waves: it crawls the raw HTML first, then queues the page for a second, deferred &lt;strong&gt;rendering pass&lt;/strong&gt; where JavaScript actually runs. That second pass can be delayed by days when crawl budget is tight, and if your content only exists after JS executes, it isn't indexed until — and unless — that pass completes.&lt;/p&gt;

&lt;p&gt;And Google is the &lt;em&gt;best-case&lt;/em&gt; crawler. The wider ecosystem is worse: many social preview bots (the ones that generate the card when someone shares your link), a number of AI crawlers, and Bing in various configurations handle JavaScript poorly or not at all. They see the blank shell and move on.&lt;/p&gt;

&lt;p&gt;That fragility shows up as concrete, revenue-affecting symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delayed or inconsistent indexing of new pages — you publish, and nothing appears in search for weeks&lt;/li&gt;
&lt;li&gt;Missing or broken social previews when links are shared, tanking click-through&lt;/li&gt;
&lt;li&gt;Meta tags, canonical URLs, and structured data that never get read because they're injected by JavaScript&lt;/li&gt;
&lt;li&gt;Content that quietly ranks below server-rendered competitors who gave the crawler an easier job&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When your rank — and the traffic that pays your bills — depends on it, "usually works" is not a strategy. This is precisely why we lean toward frameworks with first-class server rendering; it's part of the calculus in &lt;a href="https://dev.to/blog/nextjs-vs-react"&gt;Next.js vs React&lt;/a&gt; and in &lt;a href="https://dev.to/blog/choosing-a-tech-stack"&gt;how to choose a tech stack&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing SSR vs SSG
&lt;/h2&gt;

&lt;p&gt;Both are SEO-friendly — the crawler gets real HTML either way. Pick between them based on how often the content changes and whether it's personalized:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SSG&lt;/strong&gt; for content that's stable between deploys — marketing pages, landing pages, documentation, most blog posts (like this one). It's the fastest and cheapest to serve, scales infinitely from a CDN, and has almost no attack surface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSR&lt;/strong&gt; for content that's personalized or changes constantly — product pages with live inventory and pricing, search and filter results, anything user-specific with a public URL. You pay for a server render on every request, but the content is always fresh.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ISR (incremental static regeneration)&lt;/strong&gt;, offered by frameworks like &lt;strong&gt;Next.js&lt;/strong&gt;, blends the two: serve fast static HTML, but regenerate individual pages in the background on a schedule or on-demand when the data changes. This is the sweet spot for large content sites and e-commerce catalogs — thousands of pages that update periodically but don't need per-request rendering.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't have to pick one strategy for the whole site. The best setup usually mixes all three, chosen per route: static marketing pages, ISR for the product catalog, SSR for search.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's about more than crawlers
&lt;/h2&gt;

&lt;p&gt;Server rendering also improves the metrics real users feel — and that Google now measures directly. HTML that arrives ready to display produces a faster &lt;strong&gt;Largest Contentful Paint&lt;/strong&gt; and far less layout shift than a page assembled from scratch in the browser after a JavaScript download. Users on slow phones and flaky networks — a large share of the web — feel this most, and they're the ones most likely to bounce from a blank screen.&lt;/p&gt;

&lt;p&gt;Since Core Web Vitals are a ranking signal, SSR and SSG help you twice: crawlers see your content, &lt;em&gt;and&lt;/em&gt; users get a measurably faster page, which lifts both rankings and conversion. The two goals point the same direction. Our &lt;a href="https://dev.to/blog/core-web-vitals-guide"&gt;Core Web Vitals guide&lt;/a&gt; covers how to measure and hit the thresholds, and if the payload itself is the bottleneck, &lt;a href="https://dev.to/blog/caching-strategies"&gt;caching strategies&lt;/a&gt; is the next lever.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about AI search and social sharing?
&lt;/h2&gt;

&lt;p&gt;SEO used to mean Google, and Google alone could eventually run your JavaScript. That's no longer the whole game. A growing share of discovery happens through AI assistants and answer engines that crawl the web to ground their responses, through social platforms generating link previews, and through Bing powering more surfaces than its raw market share suggests. Many of these crawlers are &lt;em&gt;less&lt;/em&gt; capable at JavaScript than Googlebot, not more — they read the raw HTML and stop.&lt;/p&gt;

&lt;p&gt;That shifts the calculus further toward server rendering. If you want your content quoted by an AI assistant, previewed correctly when shared, and indexed by every engine rather than just the most sophisticated one, the safe move is to put the actual content — text, metadata, structured data — in the HTML the server sends, every time. Rendering strategy is now table stakes for being &lt;em&gt;found&lt;/em&gt; at all, not just for ranking on Google.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes even server-rendered sites make
&lt;/h2&gt;

&lt;p&gt;Rendering on the server is necessary, not sufficient. We regularly audit "SSR" sites that still leak SEO:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hydration mismatches&lt;/strong&gt; that throw the client-rendered output out of sync with the server HTML, sometimes blanking content after load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meta tags and structured data set client-side&lt;/strong&gt;, defeating the point — set them in the server response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipping a massive JS bundle anyway&lt;/strong&gt;, so hydration is slow and the page is interactive long after it looks ready.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocking crawlers in robots.txt or with aggressive bot protection&lt;/strong&gt; that also turns away Googlebot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft-404s and client-side redirects&lt;/strong&gt; that crawlers interpret as broken or missing pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to see what the crawler actually sees
&lt;/h2&gt;

&lt;p&gt;You don't have to guess. Check it directly, in order of effort:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;View source, not the DevTools inspector.&lt;/strong&gt; The Elements panel shows the &lt;em&gt;hydrated&lt;/em&gt; DOM after JavaScript runs — which always looks fine. Right-click and "View Page Source" (or &lt;code&gt;curl\&lt;/code&gt; the URL) to see the raw HTML the server actually sent. If your headline and body copy aren't in there, neither Googlebot's first pass nor a social bot will see them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Google Search Console's URL Inspection tool.&lt;/strong&gt; It shows the rendered HTML and screenshot Google produced, plus any indexing issues. This is the source of truth for what Google specifically got.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the social preview&lt;/strong&gt; by pasting your URL into a debugger (LinkedIn, Facebook, Slack all expose one). A broken card is an immediate sign your meta tags are rendered client-side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disable JavaScript&lt;/strong&gt; in your browser and reload. If the page goes blank, so does it for every crawler that doesn't run JS.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Run these before launch and after any framework or rendering change — regressions here are silent, and you often only discover them weeks later when traffic quietly fails to arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pragmatic rule
&lt;/h2&gt;

&lt;p&gt;If a page needs to be found by search or shared on social, render it on the server or at build time — full stop. If a page lives behind a login and no crawler will ever see it, CSR is perfectly fine and often simpler; there's no SEO to lose. Most real products are a deliberate mix: a server-rendered public shell — marketing, content, product pages — wrapping a client-rendered authenticated app where SEO is irrelevant.&lt;/p&gt;

&lt;p&gt;If organic traffic matters to your product and you're not certain your current stack renders for crawlers correctly, &lt;a href="https://dev.to/#contact"&gt;talk to us&lt;/a&gt; — checking exactly what Googlebot receives is one of the first things we audit, and it's often the cheapest SEO win a site can make.&lt;/p&gt;




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

</description>
      <category>web</category>
      <category>seo</category>
      <category>ssr</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to secure your web app</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:04:56 +0000</pubDate>
      <link>https://dev.to/pykero/how-to-secure-your-web-app-4749</link>
      <guid>https://dev.to/pykero/how-to-secure-your-web-app-4749</guid>
      <description>&lt;p&gt;Security is easy to treat as someone else's problem until an incident makes it very much yours. The good news is that the vast majority of real-world breaches don't come from exotic zero-days — they come from a handful of well-understood mistakes. You don't need to be a security researcher to close them. You need a baseline, applied consistently. Here's the one every web app should have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get authentication and sessions right
&lt;/h2&gt;

&lt;p&gt;Auth is where most serious failures start, so don't invent it. Use a battle-tested library or provider rather than hand-rolling password hashing and session logic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hash passwords&lt;/strong&gt; with a modern algorithm like bcrypt or argon2 — never store them reversibly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage sessions safely:&lt;/strong&gt; short-lived tokens, secure and &lt;code&gt;HttpOnly\&lt;/code&gt; cookies, and proper logout that actually invalidates the session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce authorization on the server, every time.&lt;/strong&gt; Hiding a button in the UI is not access control. Check permissions on the backend for every request, and default to deny.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Broken access control — a user reaching data or actions they shouldn't — is consistently among the most common and damaging web vulnerabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat all input as hostile
&lt;/h2&gt;

&lt;p&gt;Every piece of data from a client is a potential attack. Two classics still dominate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Injection.&lt;/strong&gt; Always use &lt;strong&gt;parameterized queries&lt;/strong&gt; or an ORM — never build SQL by concatenating strings. This shuts down SQL injection at the source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-site scripting (XSS).&lt;/strong&gt; Escape and sanitize any user-supplied content before rendering it. Modern frameworks help, but &lt;code&gt;dangerouslySetInnerHTML\&lt;/code&gt; and its equivalents will bite you.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Validate input on the server against a strict schema. Client-side validation is for user experience; it is not a security control, because anyone can bypass it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect secrets and configuration
&lt;/h2&gt;

&lt;p&gt;Credentials in your codebase are a breach waiting to be indexed. Keep secrets out of source control entirely — use environment variables or a secrets manager, and rotate them if they ever leak. Scan your repository history, not just the current files, since a key committed once lives in the history forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ship secure headers and HTTPS
&lt;/h2&gt;

&lt;p&gt;A few cheap defaults raise the floor considerably:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTPS everywhere&lt;/strong&gt;, with HSTS so browsers refuse to downgrade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security headers&lt;/strong&gt; — a Content-Security-Policy to blunt XSS, plus &lt;code&gt;X-Content-Type-Options\&lt;/code&gt; and frame protections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSRF protection&lt;/strong&gt; for cookie-based sessions, using anti-CSRF tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; on auth and other sensitive endpoints to slow brute-force attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keep dependencies honest
&lt;/h2&gt;

&lt;p&gt;Modern apps are mostly other people's code, and vulnerabilities in that code are yours to inherit. Run automated dependency scanning in CI, keep packages reasonably current, and be deliberate about what you add — every dependency is attack surface. A vulnerable library you forgot about is a common way in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it a habit, not an event
&lt;/h2&gt;

&lt;p&gt;Security isn't a checklist you complete once before launch; it's a posture you maintain. Bake these into your workflow: parameterized queries by default, secrets never committed, dependency scans in the pipeline, and authorization checks on every endpoint. Do the basics consistently and you'll be ahead of the majority of applications — and out of reach of the opportunistic attacks that cause most of the damage.&lt;/p&gt;

&lt;p&gt;If you want a security review before you ship something that matters, &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://pykero.com/blog/secure-your-web-app" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://pykero.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>security</category>
      <category>web</category>
      <category>auth</category>
    </item>
    <item>
      <title>How to scope a software project</title>
      <dc:creator>Pykero</dc:creator>
      <pubDate>Thu, 09 Jul 2026 00:59:17 +0000</pubDate>
      <link>https://dev.to/pykero/how-to-scope-a-software-project-16lb</link>
      <guid>https://dev.to/pykero/how-to-scope-a-software-project-16lb</guid>
      <description>&lt;p&gt;Almost every blown budget traces back to the same root cause: the project was never really scoped. Work started on a vague shared understanding, that understanding turned out to differ between the people paying and the people building, and the gap surfaced as overruns and hard conversations. Good scoping isn't bureaucracy — it's the cheapest insurance you can buy against a project going sideways.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope is a shared definition of "done"
&lt;/h2&gt;

&lt;p&gt;The goal of scoping is a document both sides genuinely agree on: what will be built, what won't, and how you'll know it's finished. If the client and the team would describe the deliverable differently, you don't have scope yet — you have a misunderstanding waiting to be discovered mid-build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start from outcomes, then features
&lt;/h2&gt;

&lt;p&gt;Begin with the outcomes the project must achieve, not a feature list. "Customers can subscribe and manage their own billing" is an outcome. "Add a billing page" is a feature that may or may not deliver it. Outcomes keep the conversation on value and make it obvious when a proposed feature doesn't serve one.&lt;/p&gt;

&lt;p&gt;From each outcome, derive the features actually required to reach it — and notice the ones that don't map to any outcome. Those are usually the first things to cut.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write down what's out of scope
&lt;/h2&gt;

&lt;p&gt;The most valuable section of a scope document is what it &lt;em&gt;excludes&lt;/em&gt;. Explicitly listing what you're &lt;strong&gt;not&lt;/strong&gt; building in this phase prevents the slow, silent expansion that wrecks timelines.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name the tempting features you're deliberately deferring.&lt;/li&gt;
&lt;li&gt;State the integrations, platforms, and edge cases you're not covering yet.&lt;/li&gt;
&lt;li&gt;Record assumptions — if one turns out false, that's a scope change, on the record.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Estimate in ranges, with buffer
&lt;/h2&gt;

&lt;p&gt;Single-number estimates are false precision. Estimate in ranges that reflect real uncertainty, and add explicit buffer for the unknowns you can't see yet. Break work into pieces small enough to estimate honestly — anything you can't estimate is a research task hiding as a build task, and should be spiked separately first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make change control the plan, not the exception
&lt;/h2&gt;

&lt;p&gt;Scope &lt;em&gt;will&lt;/em&gt; change — requirements always do as everyone learns more. The mistake is absorbing changes silently until the budget quietly evaporates. Instead, agree up front on a simple process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A change is proposed and written down.&lt;/li&gt;
&lt;li&gt;Its impact on timeline and cost is estimated.&lt;/li&gt;
&lt;li&gt;Both sides decide to include it, defer it, or trade it for something else.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Change control isn't friction. It's what lets you say yes to changes without lying about the timeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase it so value ships early
&lt;/h2&gt;

&lt;p&gt;Scope the smallest coherent slice that delivers real value, ship it, then scope the next. Phasing surfaces wrong assumptions while they're cheap to fix and gets working software in front of users far sooner than a big-bang delivery.&lt;/p&gt;

&lt;p&gt;At Doktouri we scope in the open — outcomes, exclusions, ranges, and a change process — so budgets hold and there are no ugly surprises at the end. If you're planning 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://pykero.com/blog/scoping-software-projects" rel="noopener noreferrer"&gt;Doktouri Agency blog&lt;/a&gt;. We build web, mobile, SaaS, and AI products — &lt;a href="https://pykero.com/#contact" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>product</category>
      <category>scoping</category>
      <category>estimation</category>
      <category>process</category>
    </item>
  </channel>
</rss>
