<?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: Oleksandr Bystrikov</title>
    <description>The latest articles on DEV Community by Oleksandr Bystrikov (@softbeehive).</description>
    <link>https://dev.to/softbeehive</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1524819%2F3c54c292-cdf1-46da-a2f5-d292b808f25b.jpeg</url>
      <title>DEV Community: Oleksandr Bystrikov</title>
      <link>https://dev.to/softbeehive</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/softbeehive"/>
    <language>en</language>
    <item>
      <title>Web application firewall on Netlify for free</title>
      <dc:creator>Oleksandr Bystrikov</dc:creator>
      <pubDate>Fri, 28 Mar 2025 08:19:15 +0000</pubDate>
      <link>https://dev.to/softbeehive/web-application-firewall-on-netlify-for-free-3m4f</link>
      <guid>https://dev.to/softbeehive/web-application-firewall-on-netlify-for-free-3m4f</guid>
      <description>&lt;p&gt;Talented folks built Netlify to reduce development friction. They offer a generous free tier, which is super helpful for the open-source ecosystem. But Netlify needs resources to grow and stay healthy. They offer paid plans and have also hired a special force called sales.&lt;/p&gt;

&lt;p&gt;Sales operate strategically. They target areas rich in special kind of prey called enterprise. Like experienced hunters, they conserve energy and wait at the watering hole called security.&lt;/p&gt;

&lt;p&gt;The internet is a wild place. And I’ve said it before — please bear with me. I like to explore the mechanics of Newtonian action and reaction in daily life.&lt;/p&gt;

&lt;p&gt;Openness and freedom create incredible opportunities for growth. And with great value comes opportunistic behavior. I personally see it as a balancing act and enjoy observing the laws of physics in many forms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Startup perspective
&lt;/h2&gt;

&lt;p&gt;Last year, I moved all my web apps from an AWS self-managed Kubernetes cluster to Netlify. Since then, I’ve saved 800€ on infrastructure bills and eliminated some complexity. In the process, I did something unusual: I tried an upsell product — GDPR-friendly site analytics for $9/month.&lt;/p&gt;

&lt;p&gt;Privacy-friendly observability is a fair deal for me. It offers insights without compromising on core values. I was surprised and genuinely puzzled by the results.&lt;/p&gt;

&lt;p&gt;It turned out that &lt;a href="https://valisa.io" rel="noopener noreferrer"&gt;Valisa&lt;/a&gt; attracted thousands of daily visitors. Most came from Singapore and the US. That was strange because my service focused on European flights. It sparked my curiosity to dig deeper.&lt;/p&gt;

&lt;p&gt;I inspected the logs and noticed distinct visitor groups: aggressive crawlers, credential harvesters, vulnerability scanners, reasonable crawlers, and legitimate users. Bad bots, good bots, and humans. My initial reaction was to wait and see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bad bots
&lt;/h2&gt;

&lt;p&gt;A few weeks in, I received an alert from Netlify. It warned me that more than half of my server functions quota (125k per month) had been used. Frankly, I like the reality in which resources are limited.&lt;/p&gt;

&lt;p&gt;In my experience, the most annoying are credential harvesters and vulnerability scanners. They fire 20-30 concurrent requests per second for around five minutes. I guess we can call it a baby DDoS.&lt;/p&gt;

&lt;p&gt;Reckless bots are the mosquitoes of the net. They get particularly active around holidays. 5k requests per day is a common occurrence, and their favorite food is WordPress.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blocked request 🚫&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://valisa.io/.git/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Amsterdam, The Netherlands&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;45.148.10.80&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blocked request 🚫&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://valisa.io/wp-content/uploads/gravity_forms/g/f/f/b/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dublin, Ireland&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;52.169.211.111&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blocked request 🚫&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://valisa.io//wp-includes/wlwmanifest.xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Boardman, United States&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;52.34.1.203&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blocked request 🚫&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://valisa.io/configs.php&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Washington, United States&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;52.146.39.115&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These requests invoke server functions. Naturally, I went to check Netlify's offer for security and was disappointed to see a "Contact Sales" button.&lt;/p&gt;

&lt;p&gt;It was like me coming to the watering hole. I see hunters; they see me. We both shrug because they are after the wild beast. And nothing happens; we continue with our daily business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build WAF
&lt;/h2&gt;

&lt;p&gt;Savanna rewards creativity. I hope this post will help founders like me build their products with fewer distractions. However, this solution is not for every scale.&lt;/p&gt;

&lt;p&gt;Netlify edge functions offer great power and flexibility. I am fascinated by how tools multiply human potential. Let's build a layered defense for the Astro website.&lt;/p&gt;

&lt;p&gt;I started by filtering out traffic targeting WordPress and PHP. Then, I focused on requests without user-agent header and those containing specific keywords like .git or .env. These rules deny access to many unwanted visitors.&lt;/p&gt;

&lt;p&gt;It took me some time to refine them by observing usage patterns. Eventually, I decided to be unwelcoming to bots that don't respect robots.txt, for example, Bytedance. Later, I blocked common automation tools for data scraping by their default user-agent.&lt;/p&gt;

&lt;p&gt;The hard part is adjusting rules for the specific use case. I remind myself that simplicity prevents bugs. But with granular control, shooting yourself in the leg is easier than ever. Security is in constant conflict with usability. Therefore, it's essential to test changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  netlify/edge-functions/firewall.ts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@netlify/edge-functions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bannedBotsRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="sr"&gt;/Bytespider|ByteDance|PetalBot|Scrapy|Go-http-client|python-requests|python-httpx|axios&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;referer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;referer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;countryCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Catch good part of total junk&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;badBot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bannedBotsRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blankUserAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="c1"&gt;// Be careful not to block file extensions you actually use&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bannedExtensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.php&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// most popular among scanners&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.cgi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.bak&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.asp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.aspx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.jsp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.py&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.rb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;// Any match in url, review before deploy&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bannedKeywords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.git&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.vscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.aws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.ssh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/wordpress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/wp-admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/wp-content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/wp-includes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;// Exact url pathnames&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bannedPathnames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/backup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/wp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;// Cool Hazker detection algorithm&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;coolHazker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bannedExtensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;bannedKeywords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;bannedPathnames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Countries&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;denyStates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bannedState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;denyStates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Alright, let's do something about it!&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bannedState&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;badBot&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;blankUserAgent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;coolHazker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blocked request 🚫&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;referer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;referer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access denied&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Review before deploy&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;excludedPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/*.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/*.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source code - &lt;a href="https://github.com/softbeehive/waf" rel="noopener noreferrer"&gt;https://github.com/softbeehive/waf&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Around eighty percent of my visitors were rogue bots. Smoking them out at the edge is an effective approach that helped me reduce server load and keep my usage within pro plan limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Efficient, does the job&lt;/li&gt;
&lt;li&gt;✅ Transparent, full control of the logic&lt;/li&gt;
&lt;li&gt;✅ Easy to customize&lt;/li&gt;
&lt;li&gt;✅ Affordable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🍋 Redeploy on changes&lt;/li&gt;
&lt;li&gt;🍋 Maintenance&lt;/li&gt;
&lt;li&gt;🍋 Prone to human errors&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;p&gt;If you like this article, give WAF a star on GitHub and consider supporting my knowledge sharing efforts. Thank you!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://paypal.me/softbeehive" rel="noopener noreferrer"&gt;Donate via PayPal&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>deno</category>
      <category>netlify</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Protect contact email from bots</title>
      <dc:creator>Oleksandr Bystrikov</dc:creator>
      <pubDate>Tue, 21 Jan 2025 13:49:41 +0000</pubDate>
      <link>https://dev.to/softbeehive/protect-contact-email-from-bots-5dj</link>
      <guid>https://dev.to/softbeehive/protect-contact-email-from-bots-5dj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lalq42onqxgvkhzwylw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lalq42onqxgvkhzwylw.png" alt="Image description" width="800" height="420"&gt;&lt;/a&gt;Internet in 2025 is a wild place. A few months before writing this, I analyzed server logs and found that up to 80% of my traffic came from bots. I blocked the most aggressive by creating a web application firewall (WAF) using Netlify Edge Functions.&lt;/p&gt;

&lt;p&gt;Make firewall rules too strict, and it will hurt legitimate visitors. So, I accept the objective reality: some bad actors will slip through.&lt;/p&gt;

&lt;p&gt;At this point, I had to decide: let machines to be trained on my knowledge without consent or keep it private. But what if I want to make some parts of that public information less accessible to data scrapers?&lt;/p&gt;

&lt;h2&gt;
  
  
  Obfuscation
&lt;/h2&gt;

&lt;p&gt;Keeping communication channels open comes with challenges. Adding a mailto link to your website is like welcoming spam. I don’t want to be distracted by another “quick follow-up”.&lt;/p&gt;

&lt;p&gt;I decided to build a second layer of defense around public contact information. The web application firewall is the first layer. And on top of that, Netlify provides its default protection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;People can copy email&lt;/li&gt;
&lt;li&gt;Works in popular browsers&lt;/li&gt;
&lt;li&gt;Easy to implement&lt;/li&gt;
&lt;li&gt;Confuses rogue bots&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Algorithm
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Split the email address into random-length chunks.&lt;/li&gt;
&lt;li&gt;Mix it with noise.&lt;/li&gt;
&lt;li&gt;Hide noise using CSS.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;I use Vue and render this part as static HTML at build stage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"chunk in address"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ciao"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nf"&gt;randomLetter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// "contact@example.org"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;con&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tac&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;t@&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;e&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ple.o&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;randomLetter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abcdefghijklmnopqrstuvwxyz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;style&lt;/span&gt; &lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.ciao&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;style&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://stackblitz.com/edit/vitejs-vite-csfoji5g?file=src%2FApp.vue" rel="noopener noreferrer"&gt;Try it on StackBlitz&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;This email is shown as copy-paste friendly text in the browser. You can apply the same technique to obscure your address or phone number.&lt;/p&gt;

&lt;p&gt;It works well at my scale. Sure, contact scrapers may evolve in the future, but interpreting CSS requires more computing resources, making it less attractive than HTML parsing.&lt;/p&gt;

&lt;p&gt;Thanks to Spencer Mortensen for comparing &lt;a href="https://spencermortensen.com/articles/email-obfuscation/" rel="noopener noreferrer"&gt;email obfuscation techniques&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;p&gt;If you like this article, consider supporting my knowledge sharing efforts and open-source work, thank you!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sponsors/softbeehive" rel="noopener noreferrer"&gt;Become a sponsor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paypal.me/softbeehive" rel="noopener noreferrer"&gt;Donate via PayPal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>frontend</category>
      <category>vue</category>
    </item>
    <item>
      <title>From zero to overkill, journey of tech founder</title>
      <dc:creator>Oleksandr Bystrikov</dc:creator>
      <pubDate>Tue, 08 Oct 2024 16:06:59 +0000</pubDate>
      <link>https://dev.to/softbeehive/from-zero-to-overkill-journey-of-tech-founder-5dm5</link>
      <guid>https://dev.to/softbeehive/from-zero-to-overkill-journey-of-tech-founder-5dm5</guid>
      <description>&lt;p&gt;Four years ago, I left my software engineering job to work on an indie project called &lt;a href="https://valisa.io" rel="noopener noreferrer"&gt;Valisa&lt;/a&gt; – a travel startup that helps people find the best fares and plan their trips faster. The goal is to save travelers' time.&lt;/p&gt;

&lt;p&gt;I want to share bits of my co-founder/solopreneur story. How we engineered it, what worked and what didn't, and why I reevaluated the company path. This can be useful to tech founders who know how to code.&lt;/p&gt;

&lt;p&gt;In &lt;em&gt;"Down and Out in Paris and London,"&lt;/em&gt; George Orwell claimed that French restaurants thrive thanks to their sharp knives. It can well be British humor directed at their favorite neighbors. But I enjoy the idea that competitive advantage wasn't the chef's ability to mix cheese and wine.&lt;/p&gt;

&lt;p&gt;If human experiences are Brownian motion, the journey from start to value is like riding the atom with a jetpack. Hitting other particles that affect my path is inevitable. I have an engine for maneuvering. And I must get across before running out of fuel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tuning engine in flight
&lt;/h2&gt;

&lt;p&gt;During the lunch discussion, my friend Siarhei and I discovered that we faced exact problems while looking for affordable flights and hotels. Trip preparation requires a ton of energy and takes time.&lt;/p&gt;

&lt;p&gt;Comparing options and booking tickets is messy due to context switching. I take notes to ensure things align and trip events make sense. But notes are an old-school solution. There is room for improvement. We decided we could automate our travel planning routine.&lt;/p&gt;

&lt;p&gt;It took us a year to go from idea to alpha. Valisa 1.0 was a minimalist fare finder that covered European budget airlines: Ryanair and easyJet. The feedback we have received was overwhelmingly positive. We decided to focus on adding more providers and optimizing for scale.&lt;/p&gt;

&lt;p&gt;A classic mistake? Let's dive into details so it doesn't sound like a generic statement. Our microservices were written in Go. They all served a single client – a Vue single-page app. I pre-rendered landing pages. But SPA and search engines don't play well together. JavaScript frameworks didn't have good server-side rendering support at that time.&lt;/p&gt;

&lt;p&gt;I invested more than a month of work into switching to SSR. Nuxt 2 hydration errors annoyed me so much that I dropped it and went for an ugly wrapper that added social preview tags.&lt;/p&gt;

&lt;p&gt;Siarhei set up Kubernetes cluster on Google Cloud. With monthly costs of around 120-140 euros, it didn't seem like a big deal. Then AWS approved our application. We got $4.000 in credits and moved all infrastructure there. Adding blows and whistles in the process, of course. &lt;/p&gt;

&lt;p&gt;Valisa had a load balancer, Redis cache, infrastructure as code via Terraform, staging, observability, monitoring, and a Kanban board. It also had zero paying customers!&lt;/p&gt;

&lt;p&gt;Clean code, best practices, refactoring, and optimization are pillars of our belief system, aren't they? In the eyes of an engineer, these tasks are crucial. But customers don't care about hidden details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lost year
&lt;/h2&gt;

&lt;p&gt;In late 2021, I started working on the planner designed to complement the fare finder. By winter, we had UI mock-ups. Siarhei started implementing auth using Ruby on Rails. After several setbacks, we concluded reinventing the wheel was a terrible idea. Ory Kratos seemed a decent option, but it was dismissed due to the complexity overhead.&lt;/p&gt;

&lt;p&gt;Events that happened after shattered all our plans. Sick neighbors invaded Ukraine full-scale in February 2022. My priorities and values shifted overnight. I got busy helping my family, friends, and random people.&lt;/p&gt;

&lt;p&gt;Valisa was put on hold. I barely touched the code. Siarhei decided to concentrate on his full-time job. I worked as a freelancer and organized direct aid. Arguing in German, opening the locked car with a broom. None of my jobs taught me that.&lt;/p&gt;

&lt;p&gt;Not every day do you lose your home. Stress affected my ability to think, so I had to find a way to manage my worries. It's a true paradox: simple actions like running or working ease the pressure. I discovered this built-in protection mechanism, and it helped me restart Valisa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Product
&lt;/h2&gt;

&lt;p&gt;I'm puzzled when people brag about the simplicity of their work. "I built a revolutionary app over the weekend." Is it any good? I embrace a different approach. Conservation law is never going to let you down.&lt;/p&gt;

&lt;p&gt;Let's leave the land of flower-eating unicorns for a moment. We need to see the dark side of the moon. Adventurers know that assembling bits into a coherent travel experience is tricky.&lt;/p&gt;

&lt;p&gt;I wanted to undo the initial system design mistakes and use isomorphic HTML rendering for SEO. Astro was the perfect choice due to Vue support. So, I started a move from SPA to TypeScript SSR.&lt;/p&gt;

&lt;p&gt;A few months into the transition, AWS credits expired. Infrastructure bills quickly grew from 60 to 100 euros a month with no spike in usage. Amazon started charging customers for public IPs and adjusted prices for inflation.&lt;/p&gt;

&lt;p&gt;Valisa didn't really need Kubernetes in the first place. I decided to use an opportunity to try Netlify instead. However, I had to move the web app logic and create new bugs. Siarhei deployed Go services to Cloud Run. I tested it locally and found that backend cold start time was reasonable.&lt;/p&gt;

&lt;p&gt;In August 2024, I finished major tasks and shipped it. This caused a short downtime due to switching DNS from Route 53 to Netlify, but overall, everything was smooth. Infra costs sunk to 20 euros per month.&lt;/p&gt;

&lt;p&gt;I have a design system based on Material 3, better fare page, registration, updated landing and copy, and much simpler architecture. As I drew the line and looked back, I realized something.&lt;/p&gt;

&lt;p&gt;Enough of modern tech experiments. I must build the product!&lt;/p&gt;

&lt;h2&gt;
  
  
  Good bits
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;This is ten percent luck, twenty percent skill&lt;br&gt;
Fifteen percent concentrated power of will&lt;br&gt;
Five percent pleasure, fifty percent pain&lt;br&gt;
And a hundred percent reason to remember the &lt;del&gt;name&lt;/del&gt; day  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;– Fort Minor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Starting an indie company and embracing a healthy economic model without VC money was the best decision ever. Small teams and solopreneurs can be incredibly productive.&lt;/p&gt;

&lt;p&gt;The Estonian e-Residency program delivered awesome onboarding. We had an excellent experience with our accounting provider. A friendly business climate makes a difference between life and death for a small company.&lt;/p&gt;

&lt;p&gt;Something that was once a weakness can become a strength. Valisa's modern tech stack is ready for scale. Tailwind UI license is definitely worth the investment. I enjoy having a design system while building a planner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learnings
&lt;/h2&gt;

&lt;p&gt;We are riding an atom with a jetpack. And it was engine tuning in flight. The cost of this learning is high. We committed resources to secondary tasks and played the over-engineering game. Seeing the bigger picture is way more important. I appreciate this eye-opening experience.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Focus on core product
&lt;/li&gt;
&lt;li&gt;Avoid unnecessary complexity
&lt;/li&gt;
&lt;li&gt;Reject side hustles
&lt;/li&gt;
&lt;li&gt;Done is better than perfect
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What could be better than solving a problem that frustrated you? To find an answer, you should try. Social pressure, financial constraints, and geopolitical events affected my path. A healthy portion of adventurism and strong motivation help sail storm and still.&lt;/p&gt;

&lt;p&gt;Samurai has no drone – only a remote control.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>astro</category>
      <category>vue</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Innovation-friendly software</title>
      <dc:creator>Oleksandr Bystrikov</dc:creator>
      <pubDate>Fri, 24 May 2024 15:05:36 +0000</pubDate>
      <link>https://dev.to/softbeehive/innovation-friendly-software-p8l</link>
      <guid>https://dev.to/softbeehive/innovation-friendly-software-p8l</guid>
      <description>&lt;p&gt;Let’s face it – many established companies are live software museums. Marketing often advertises it as cutting-edge, state-of-the-art, and crème de la crème. But under the hood, in the age of AI, our world still relies on vintage technology.&lt;/p&gt;

&lt;p&gt;Chemical terminals use 25-year-old tools that work in Internet Explorer 5 only, trains run on Windows XP, and who knows what Perls you may find in a traditional banking sector?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why on Earth?
&lt;/h2&gt;

&lt;p&gt;I have a theory: when businesses buy products or services, their expectations are proportional to the commitment. One does not simply take away a tool folks used for decades. This phenomenon is called &lt;strong&gt;&lt;em&gt;technological inertia&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then, one day, customers riot, demanding high-speed wi-fi on the Win XP train. You order golden routers and do a lab test. During the rollout, two million people join the network. The electric system fails. Trying to fix lights, you break doors. The release reverted via USB drive downgrade on 250 trains, 13 bricked in the process. Passengers are happy the doors work again.&lt;/p&gt;

&lt;p&gt;Meet &lt;strong&gt;&lt;em&gt;sunk cost fallacy&lt;/em&gt;&lt;/strong&gt;: individuals or organizations may have invested heavily in the existing technology. And they are unwilling to abandon that investment, even if a new solution would be more cost-effective in the long run.&lt;/p&gt;

&lt;p&gt;The point of no return is called don’t touch if it works. The longer it remains without a change, the more untouchable it gets. Crossing this Rubicon means an expensive and lengthy upgrade ahead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vehicle management at car2go
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulg715n9bqpuewh05ajb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulg715n9bqpuewh05ajb.jpg" alt="car2go smart for two in Berlin" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At car2go (carsharing company), I worked in the vehicle lifecycle management team. We took ownership of a relatively fresh Angular 1 single-page app developed by an external agency.&lt;/p&gt;

&lt;p&gt;Our motivated team started making changes. I remember applying a fix that caused seven new bugs. This pattern repeated again and again. Skilled and experienced engineers could not maintain the stability bar set by the company. Only manual QA saved us because automated tests did not catch issues.&lt;/p&gt;

&lt;p&gt;The situation was spiraling out of control. We proceeded with caution, and that slowed our progress. During retrospectives, we began questioning our course of action. Some code was relatively fresh, though the main pain point was data binding side-effects in convoluted controllers.&lt;/p&gt;

&lt;p&gt;On the API side, challenges were more significant – multiple databases were out of sync, critical bugs in queue processing, inconsistent data, and limited observability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Big bang is not an option
&lt;/h3&gt;

&lt;p&gt;Our team concluded it was pragmatic to rethink the architecture. We needed modularity to achieve a better user experience. And a trunk-compatible system design that makes daily releases possible.&lt;/p&gt;

&lt;p&gt;The head of engineering recognized the added value of the transition to innovation-friendlier architecture. Five engineers and a PO with QA and PM support performed zero downtime incremental upgrades that took 1.5 years to complete. For vehicle management, it was an investment that paid off.&lt;/p&gt;

&lt;p&gt;When you operate a fleet of expensive cars, the difference between email damage tracking and an automated system could be millions of euros per year. According to my calculations, the return on investment was around 5-7x.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to keep up with the progress?
&lt;/h2&gt;

&lt;p&gt;Recently, micro-service architecture has been a subject of wide criticism. Watch this brilliant video.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/y8OnoxKotPQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Michael Paulson, aka The Primeagen, a prominent yelling tech figure, likes to mock startups for having more services than users.&lt;/p&gt;

&lt;p&gt;The core issue is not a particular architecture. Humans associate failures with things they dislike. Be it a monolith, micro-services, or a framework. But the true killer is the complexity.&lt;/p&gt;

&lt;p&gt;I see a repeating pattern occurring in established companies. Software is left unattended for some time. People join and leave, and priorities shift. Suddenly, a vintage marvel becomes a development blocker because multiple critical parts depend on it. Organizations invest astronomical amounts into legacy system integration.&lt;/p&gt;

&lt;p&gt;Take action before it’s too late, and keep making changes as the product evolves. Motivated people recognize when something doesn’t work. When it happens, the lead must make a calculated decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to address it?
&lt;/h2&gt;

&lt;p&gt;There is a better alternative – invest in reasonable modularity. I build high-performance, innovation-friendly software that helps companies avoid expensive upgrades and legacy service pain. Estimated ROI within five years +600%.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://softbeehive.com/about" rel="noopener noreferrer"&gt;Hire me&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5s0qgsd50wf4qdog7d2n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5s0qgsd50wf4qdog7d2n.png" alt="system design" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Does it resonate with your experience? I'm curious to hear how you integrate vintage software. And how do you keep up with the WILD pace of change in the web tooling?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>microservices</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
