<?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: Petra Grunheidt</title>
    <description>The latest articles on DEV Community by Petra Grunheidt (@petragrunheidt).</description>
    <link>https://dev.to/petragrunheidt</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%2F1068634%2Fc1f8929c-e480-4f47-a52e-fedd1f7bc117.jpeg</url>
      <title>DEV Community: Petra Grunheidt</title>
      <link>https://dev.to/petragrunheidt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/petragrunheidt"/>
    <language>en</language>
    <item>
      <title>Geolocation in web apps made easy with haversine</title>
      <dc:creator>Petra Grunheidt</dc:creator>
      <pubDate>Mon, 04 Nov 2024 16:59:30 +0000</pubDate>
      <link>https://dev.to/petragrunheidt/geolocation-in-web-apps-made-easy-with-haversine-2bc3</link>
      <guid>https://dev.to/petragrunheidt/geolocation-in-web-apps-made-easy-with-haversine-2bc3</guid>
      <description>&lt;p&gt;In this post, I will be implementing a solution to find the estimated distance of a user to &lt;strong&gt;Places&lt;/strong&gt; saved in my application.&lt;/p&gt;

&lt;p&gt;For simplicity's sake, let's imagine we have a web app that displays many restaurants from various cities and states. The web app is connected to a backend service in Rails with a relational database. Before the implementation of the geolocation feature, restaurants were displayed in a random order, which means a user might see a restaurant from another state before seeing one they are more likely to visit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The requirements for the feature are:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a user accesses the app, they will have the option to share their geolocation.&lt;/li&gt;
&lt;li&gt;Upon accepting, the page should be more personalized based on the distance of the user to the restaurants.&lt;/li&gt;
&lt;li&gt;The location doesn't have to be precise, like GPS precision, but it shouldn't be imprecise within a 0.5 km margin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Collecting the user's location
&lt;/h3&gt;

&lt;p&gt;In the context of a web-app, the easiest way to collect a user's estimated location is using the browser's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API" rel="noopener noreferrer"&gt;Geolocation API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Calling the &lt;code&gt;getCurrentPosition&lt;/code&gt; or &lt;code&gt;watchCurrentPosition&lt;/code&gt; functions will prompt the user to allow or deny sharing their geolocation&lt;br&gt;
Here is a sample implementation that should prompt the user to share their Geolocation, saves it to &lt;code&gt;localStorage&lt;/code&gt; and handles errors with &lt;code&gt;console.log&lt;/code&gt;&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getGeolocation&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geolocation&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;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geolocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;position&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;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;longitude&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;geolocation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nf"&gt;reject&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User denied the request for Geolocation.&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nf"&gt;reject&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;An error occurred while retrieving location.&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="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Geolocation is not supported by this browser.&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;The data retrieved by this function is the user's approximate &lt;strong&gt;latitude&lt;/strong&gt; and &lt;strong&gt;longitude&lt;/strong&gt;. Keep in mind, this location data can be derived from various sources such as GPS, WiFi, or the user's internet connection network, so it may not always be highly precise. However, it is accurate enough for our requirements and for most e-commerce apps.&lt;/p&gt;

&lt;p&gt;You can then send these coordinates to your backend, where you can handle the proximity of the restaurants with an appropriate query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sorting Records by Geolocation
&lt;/h3&gt;

&lt;p&gt;The defined requirement is that the page should be more personalized based on location. One way to do that is by ordering the Restaurants by proximity.&lt;br&gt;
Now consider that we have a &lt;code&gt;Restaurants&lt;/code&gt; table with address information, such as city, state, street address, but no coordinates. How can we find the distance between coordinates and an address?&lt;/p&gt;

&lt;p&gt;There are ways to find the distance between two coordinates, such as the Haversine formula, which is a trigonometric function to find the distance between two points in a perfect sphere. The earth isn't exactly a sphere, but given our precision requirements it's close enough to a sphere. If you want to read more about a precise calculation of Geolocation, you can checkout &lt;a href="https://postgis.net/" rel="noopener noreferrer"&gt;Postgis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A possible implementation of the Haversine formula looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_by_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"restaurants.*, 
            (6371 * acos(cos(radians(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)) * cos(radians(latitude)) * 
            cos(radians(longitude) - radians(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)) + 
            sin(radians(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)) * sin(radians(latitude)))) AS distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an active record query on the Restaurant model that takes as argument latitude and longitude and compares it to that of all Restaurant records, calculating the distance. It then orders restaurants by the closest ones.&lt;br&gt;
It is based off of this post on &lt;a href="https://fonini.github.io/2017/04/03/formula-haversine-postgresql" rel="noopener noreferrer"&gt;applying Haversine to SQL queries&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Luckily, the Postgres team has already implemented this, and it can be used by adding the earthdistance extension.&lt;/p&gt;

&lt;p&gt;If you're using Rails and Postgres, you would need a migration like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddsEarthdistanceExtensionToPostgres&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;enable_extension&lt;/span&gt; &lt;span class="s1"&gt;'cube'&lt;/span&gt; &lt;span class="c1"&gt;#a dependency of the earthdistance extension &lt;/span&gt;
    &lt;span class="n"&gt;enable_extension&lt;/span&gt; &lt;span class="s1"&gt;'earthdistance'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The previous query with the extension looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_by_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"restaurants.*, 
          earth_distance(ll_to_earth(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;), 
          ll_to_earth(latitude, longitude)) 
          AS distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'distance'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ll_to_earth&lt;/code&gt; function, transforms &lt;strong&gt;latitude&lt;/strong&gt; and &lt;strong&gt;longitude&lt;/strong&gt; values to spatial coordinates, and the &lt;code&gt;earth_distance&lt;/code&gt; function calculates the distance between these coordinates using the Haversine formula.&lt;/p&gt;

&lt;p&gt;This formula is also implemented in many other databases, for instance &lt;a href="https://redis.io/docs/latest/commands/geodist/" rel="noopener noreferrer"&gt;Redis GEODIST&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Address to coordinates
&lt;/h3&gt;

&lt;p&gt;Ok, now we know how to find the distance between coordinates, and we know how to find the user's coordinates, but we don't have the Restaurant's coordinates. How do we get them?&lt;/p&gt;

&lt;p&gt;There are many apis that can be used to get the coordinates of an address. Most of them are freemium services. Here are some of the most popular ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/documentation/geocoding/overview" rel="noopener noreferrer"&gt;Google Maps Geocoding API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openstreetmap.org/" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can test a few records yourself using Google Maps on their web page to see how it works. In my implementation, I chose Google Maps because it handles various address formats and styles more flexibly than other apis.&lt;/p&gt;

&lt;p&gt;That's it, now we have everything we need to implement our geolocation feature, just plug these coordinates into your Restaurants and we are good to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API" rel="noopener noreferrer"&gt;Geolocation API&lt;/a&gt;.&lt;br&gt;
  &lt;a href="https://fonini.github.io/2017/04/03/formula-haversine-postgresql" rel="noopener noreferrer"&gt;Haversine formula&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://postindustria.com/postgresql-geo-queries-made-easy/" rel="noopener noreferrer"&gt;Postindustria geo queries made easy&lt;/a&gt;&lt;br&gt;
&lt;a href="https://postgis.net/" rel="noopener noreferrer"&gt;Postgis&lt;/a&gt;&lt;br&gt;
&lt;a href="https://redis.io/docs/latest/commands/geodist/" rel="noopener noreferrer"&gt;Redis GEODIST&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://developers.google.com/maps/documentation/geocoding/overview" rel="noopener noreferrer"&gt;Google Maps Geocoding API&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.openstreetmap.org/" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>database</category>
    </item>
    <item>
      <title>Obfuscating your create react app and routes</title>
      <dc:creator>Petra Grunheidt</dc:creator>
      <pubDate>Wed, 17 Jan 2024 14:40:06 +0000</pubDate>
      <link>https://dev.to/petragrunheidt/obfuscating-your-react-app-and-routes-1hp4</link>
      <guid>https://dev.to/petragrunheidt/obfuscating-your-react-app-and-routes-1hp4</guid>
      <description>&lt;p&gt;This week, I was tasked with enhancing the security of our Create React App by employing obfuscation and minification techniques.&lt;/p&gt;

&lt;p&gt;Obfuscation, as the name implies, obscures the source code, intentionally making it complex for humans to decipher while retaining its functionality. Minification, on the flip side, compresses code, trims the fat, and optimizes size for efficient delivery.&lt;/p&gt;

&lt;p&gt;During my intial search i came across some outdated libraries like &lt;code&gt;javascript-obfuscator&lt;/code&gt; and &lt;code&gt;uglify-js&lt;/code&gt;(as if javascript code can get any uglier, am I right?). Then, I stumbled upon &lt;a href="https://terser.org/" rel="noopener noreferrer"&gt;Terser&lt;/a&gt;, a modern library that supports ES6.&lt;/p&gt;

&lt;p&gt;Reading through the documentation I was able to apply a simple enough script for my &lt;code&gt;package.json&lt;/code&gt;:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;obfuscate:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;terser build/static/js/main.*.js --compress --mangle --output main.min.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which I then tinkered with using xargs for it to update the same file that was used instead of generating a new one:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;obfuscate&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;find build/static/js -type f -name 'main.*.js' -print0 | xargs -0 -I {} terser {} --compress --mangle --output {}&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;I also came across an article recommending setting the &lt;code&gt;GENERATE_SOURCEMAP&lt;/code&gt; to false. A source map is a file that maps the minified or transpiled code back to its original source code, aiding in debugging. While source maps may be valuable during development for debugging purposes, they can potentially expose sensitive information about your code structure and logic. So my final script looks like this:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&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;GENERATE_SOURCEMAP=false react-scripts build &amp;amp;&amp;amp; yarn obfuscate&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;Now when I open the website, the files are obfuscated and minified:&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%2Fahmgirn02q9uxbgr5hfe.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%2Fahmgirn02q9uxbgr5hfe.png" alt="Obfuscated build" width="800" height="427"&gt;&lt;/a&gt;&lt;br&gt;
(Apologies for the light mode, i don't usually use the browser i printed this on. This is not an obfuscation technique, as it is local to my browser)&lt;/p&gt;

&lt;p&gt;So, job well done, right?&lt;/p&gt;

&lt;p&gt;However, a colleague conducting security checks on the application discovered that it remained remarkably easy to discern all the routes by searching for the path. This is understandable since the route names can't be effectively obfuscated, as the pathnames must be preserved for users to be able to access the routes. While this is acceptable for public routes, every file in the application is somehow present in the &lt;code&gt;/build/static/js/main.*.js&lt;/code&gt; file, given that it's a JavaScript SPA.&lt;/p&gt;

&lt;p&gt;The issue arose because our app contained routes meant to be 'secret' and hidden from regular users. Although these routes were protected by authentication, exposing them wasn't an ideal scenario&lt;/p&gt;
&lt;h2&gt;
  
  
  Chunkification
&lt;/h2&gt;

&lt;p&gt;Now, here comes the really interesting part of this article. The key to obfuscating routes lies in leveraging the power of React's &lt;a href="https://react.dev/reference/react/lazy" rel="noopener noreferrer"&gt;&lt;strong&gt;lazy&lt;/strong&gt;&lt;/a&gt; loading and &lt;a href="//ttps://react.dev/reference/react/Suspense"&gt;&lt;strong&gt;suspense&lt;/strong&gt;&lt;/a&gt; mechanisms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lazy Loading&lt;/strong&gt;: Lazy loading allows you to load specific parts of your application only when they are needed. This is particularly beneficial for large applications with numerous components. React's &lt;code&gt;lazy&lt;/code&gt; function enables you to dynamically import components, ensuring that they are only fetched and rendered when required. This helps in reducing the initial loading time and improving the overall performance of your application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Suspense&lt;/strong&gt;: Suspense is a React feature that allows components to wait for something before rendering. It enables you to manage the loading states of your components more efficiently. With the &lt;code&gt;Suspense&lt;/code&gt; component, you can specify fallback content to display while waiting for the main content to load. This creates a smoother and more seamless user experience, especially when dealing with asynchronous operations such as lazy loading.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By strategically employing these features, we can dynamically split and load code associated with specific routes into separate chunks within your &lt;code&gt;build/static&lt;/code&gt; folder, and it will only be loaded when the route is accessed. This not only enhances the security of your application but also optimizes the loading of resources, providing a more efficient and seamless user experience.&lt;/p&gt;

&lt;p&gt;When applying this technique and building the app, our &lt;code&gt;Chunk&lt;/code&gt; will appear like this:&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%2Fghwyjtfl092qgq3c0m7e.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%2Fghwyjtfl092qgq3c0m7e.png" alt="Chunky the Chunk" width="375" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's go to the implementation&lt;/p&gt;
&lt;h3&gt;
  
  
  Implementation in Routing
&lt;/h3&gt;

&lt;p&gt;In your main routing configuration (&lt;code&gt;app/index.js&lt;/code&gt;), you can structure it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/index.js&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/*"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IndexRoutes&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Regular routes */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/admin/*"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&amp;gt;&amp;lt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AdminRoutes&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Routes you want to hide */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AdminRoutes component, which contains the routes you wish to secure (routes/admin.jsx), could look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/admin.jsx&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/secret-clients/:slug"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AdministrativePage&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/super-secret-page"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SuperSecretPage&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach allows the existence of the /admin namespace to be evident in your &lt;code&gt;main.*.js&lt;/code&gt; file. However, the actual code related to this namespace is encapsulated in a separate chunk that won't load until the corresponding routes are accessed.&lt;/p&gt;

&lt;p&gt;There is just one potential issue: While our admin chunk is inaccessible from regular routes, accessing &lt;code&gt;host/admin/{anything}&lt;/code&gt; would load a page. The page would be blank, but unfortunately for us, this also triggers the loading of the associated chunk, potentially exposing information about the routes.&lt;/p&gt;

&lt;p&gt;To add an extra layer of protection against unauthorized access to our chunk, we can implement a redirect strategy within our &lt;code&gt;routes/admin.jsx&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// routes/admin.jsx&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/actions/:slug"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AdministrativePage&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/super-secret-page"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SuperSecretPage&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Navigate&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By including &lt;code&gt;&amp;lt;Route path="*" element={&amp;lt;Navigate to="/" /&amp;gt;} /&amp;gt;&lt;/code&gt;, we ensure that any attempt to access our chunk with an unknown path will result in an immediate redirect to the homepage. While this provides improved security, there's still a brief moment when the chunk is accessible before the redirect occurs, and you know hackers these days, with their black hoodies and big brains. If they can download cars, they could certainly download our chunk if they tried hard enough.&lt;/p&gt;

&lt;p&gt;To further enhance security, we can integrate this strategy with user authentication. Returning to our &lt;code&gt;app/index.js&lt;/code&gt;, we can modify the routing configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isAdminLoggedIn&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="s1"&gt;./dir&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="cm"&gt;/* function to verify authentication*/&lt;/span&gt;

&lt;span class="p"&gt;{...}&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/*"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IndexRoutes&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Regular routes */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/auth/*"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IndexRoutes&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Auth routes */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;isAdminLoggedIn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/admin/*"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&amp;gt;&amp;lt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AdminRoutes&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Routes you want to hide */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By implementing authentication in combination to the chunk strategy, even if an unauthenticated user were to try and access the &lt;code&gt;/admin&lt;/code&gt; namespace, it would not trigger the chunk loading, therefore, protecting our app further.&lt;/p&gt;

</description>
      <category>security</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Effective email HTML and image handling</title>
      <dc:creator>Petra Grunheidt</dc:creator>
      <pubDate>Mon, 04 Dec 2023 12:15:11 +0000</pubDate>
      <link>https://dev.to/petragrunheidt/effective-email-html-and-image-handling-3ihe</link>
      <guid>https://dev.to/petragrunheidt/effective-email-html-and-image-handling-3ihe</guid>
      <description>&lt;p&gt;If you are trying to fire an email you coded with all of the beautiful assets your UX team provided, only to find an unaligned mess, without images when opening it on your email provider, this article is for you.&lt;/p&gt;

&lt;p&gt;The reason behind this is quite simple: Modern CSS is not supported in most email providers. As a developer of the 2020s, you're likely accustomed to aligning your pages with flexbox, grid or other modern CSS features.&lt;/p&gt;

&lt;p&gt;Now, on reading the above statement you might be thinking this is probably an issue with small email providers that nobody uses anyway so why should you care?&lt;/p&gt;

&lt;p&gt;To our dismay, major platforms like Gmail do not support flexbox align-items, and there are no plans to include support for SVG (and don't get me started with outlook's desktop app, where even basic styles go very wrong). You can refer to an illustrative guide of what you can or can't do across email providers on &lt;a href="https://www.caniemail.com/" rel="noopener noreferrer"&gt;Caniemail&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I first learned about these issues in a presentation by a work colleage (&lt;a class="mentioned-user" href="https://dev.to/mfornaciari"&gt;@mfornaciari&lt;/a&gt;), and was later on assigned a task to develop e-mails for an e-commerce app. This article is a way for me to summarize the main points I learned while studying for my tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic HTML Alignment and Layout
&lt;/h3&gt;

&lt;p&gt;So, if you can't use modern CSS, what is the go to way of aligning emails? the answer is, a simple html table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt;
    &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"presentation"&lt;/span&gt;
    &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt;
    &lt;span class="na"&gt;border=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
    &lt;span class="na"&gt;cellpadding=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
    &lt;span class="na"&gt;cellspacing=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
    &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"min-width: 100%;"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- since we are using the table tag only for styling, it is important for acessibility purposes to use a semantic role --&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- the other properties, except for the width, are style resets --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;align=&lt;/span&gt;&lt;span class="s"&gt;"center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        {content}
      &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple centered table layout is widely supported and ensures a consistent appearance across various email clients, even those that might not interpret advanced styling techniques.&lt;/p&gt;

&lt;p&gt;With this structure, I separated my email content into rows. Since media queries are not reliable for emails, it's best to think of them as a verticalized media that must fit web and mobile screen resolutions. For this reason, it is a common practice to limit an email's body width to 600px.&lt;/p&gt;

&lt;p&gt;Since I was using Ruby on Rails, I added this rule to my mailer layout file along with basic text styles and style resets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"text/html; charset=utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;

            &lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Urbanist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.75&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Urbanist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&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;/style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;'presentation'&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;'100%'&lt;/span&gt; &lt;span class="na"&gt;border=&lt;/span&gt;&lt;span class="s"&gt;'0'&lt;/span&gt; &lt;span class="na"&gt;cellpadding=&lt;/span&gt;&lt;span class="s"&gt;'0'&lt;/span&gt; &lt;span class="na"&gt;cellspacing=&lt;/span&gt;&lt;span class="s"&gt;'0'&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;'background: grey; min-width: 100%;'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;align=&lt;/span&gt;&lt;span class="s"&gt;'center'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;'presentation'&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;'100%'&lt;/span&gt; &lt;span class="na"&gt;border=&lt;/span&gt;&lt;span class="s"&gt;'0'&lt;/span&gt; &lt;span class="na"&gt;cellpadding=&lt;/span&gt;&lt;span class="s"&gt;'0'&lt;/span&gt; &lt;span class="na"&gt;cellspacing=&lt;/span&gt;&lt;span class="s"&gt;'0'&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;'background: #fff; max-width: 600px'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;align=&lt;/span&gt;&lt;span class="s"&gt;'center'&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="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;yield&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- For those unfamiliar with html.erb, this is where the content of my specific e-mails will go --&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, lets create a simple welcome mail and check out the result in mailcatcher (keep in mind that in a real use scenario, you should test that your email actually worked in real providers):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;      &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;

        &lt;span class="c"&gt;&amp;lt;!-- header, this can be extracted into a shared partial --&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt;
              &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"presentation"&lt;/span&gt;
              &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt;
              &lt;span class="na"&gt;border=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
              &lt;span class="na"&gt;cellpadding=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
              &lt;span class="na"&gt;cellspacing=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
              &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"min-width: 100%;"&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;align=&lt;/span&gt;&lt;span class="s"&gt;"center"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;'background: red'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;'color: yellow'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Super e-commerce&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;

          &lt;span class="c"&gt;&amp;lt;!-- content --&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt;
            &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"presentation"&lt;/span&gt;
            &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt;
            &lt;span class="na"&gt;border=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
            &lt;span class="na"&gt;cellpadding=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
            &lt;span class="na"&gt;cellspacing=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
            &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"min-width: 100%;"&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;align=&lt;/span&gt;&lt;span class="s"&gt;"center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome to this plataform, champ&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Thanks for joining, you're a real superstar, we love you&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;

          &lt;span class="c"&gt;&amp;lt;!-- footer, this can be extracted into a shared partial --&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;footer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt;
              &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"presentation"&lt;/span&gt;
              &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt;
              &lt;span class="na"&gt;border=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
              &lt;span class="na"&gt;cellpadding=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
              &lt;span class="na"&gt;cellspacing=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
              &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"min-width: 100%;"&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;align=&lt;/span&gt;&lt;span class="s"&gt;"center"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;'background: red'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;'color: yellow'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;cool logo&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now let's fire our email, aaaand:&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%2Faev0hrdkv9ta9fls2zc2.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%2Faev0hrdkv9ta9fls2zc2.png" alt="Sample email" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your UX team might have a murderous instinc towards me upon seeing this, but that's what healthy emails are made of.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strategies for Handling Images
&lt;/h3&gt;

&lt;p&gt;Another issue I ran into was dealing with images. As I mentioned at the beginning of this article, Gmail does not support inline image methods such as SVG or base64.&lt;/p&gt;

&lt;p&gt;Also, emails have a limited size limit; Gmail, for instance, allows only a maximum of 25MB per email. Additionally, if you send multiple emails with the same subject, for instance, a user spamming password recovery emails and receiving many stacked emails with (1), (2)..., this limit counts for all the collective emails, not for each one.&lt;/p&gt;

&lt;p&gt;Here are the two best approaches I found for handling images&lt;/p&gt;

&lt;h4&gt;
  
  
  Image File attachment
&lt;/h4&gt;

&lt;p&gt;When it comes to including images in your emails, one simple method is to attach image files directly to the email. This allows the recipient to download and view the images separately. Keep in mind that the total size of email attachments may be subject to limitations imposed by email service providers.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;YourMailer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationMailer&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_email_with_attachment&lt;/span&gt;
        &lt;span class="c1"&gt;# Other mail settings (to, from, subject, etc.)&lt;/span&gt;
        &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'recipient@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="s1"&gt;'Email with Attachment'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="c1"&gt;# Your HTML content here&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;

          &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="c1"&gt;# Your plain text content here&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# Attach the image file&lt;/span&gt;
        &lt;span class="n"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'image.jpg'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'path/to/image.jpg'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I feel like this method should be used with some consideration, since it increases the total size of your email.&lt;br&gt;
  Additionally, it may not be ideal to clutter your email with image attachments, considering that the final user isn't intended to download an Instagram logo, for example.&lt;/p&gt;
&lt;h4&gt;
  
  
  External URL Hosting
&lt;/h4&gt;

&lt;p&gt;The approach I find most effective for all image sizes, is storing them in an external host such as on an S3 bucket, google drive, a github repository or imgur. This approach doesn't impact the email's full size limit, providing a more scalable solution, but the external services apis may have requests and bandwith limitations and may not be free.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://external-hosting.com/path/to/image.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"External Image"&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;The challenge I found with this approach is that free services for hosting are not really optimized for this use and may lead to broken images in your email.&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%2Fxnxnvttsr4tb5n2p64vm.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%2Fxnxnvttsr4tb5n2p64vm.png" alt="Broken image example" width="750" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The mobile screenshot above depicts the footer of the email I was developing for my task. It was designed to feature a company logo and three social media links. Initially, I chose to use Google Drive to store my images because it's free. &lt;/p&gt;

&lt;p&gt;While the web version of my app consistently loaded images upon most page reloads, the mobile version experienced frequent breaks. Unfortunately, relying on a simple JavaScript reload for images when a request fails is not an available feature in email providers, so the first request on your img tag must succeed.&lt;/p&gt;

&lt;p&gt;Consequently, I shifted to a paid solution – the S3 bucket. Although there is a cost associated with this approach, it proved to be a reliable solution for image loading. At first this solution did not seem ideal for someone like me who does not really believe in paying for stuff (or, in Brazilian Portuguese, a '&lt;a href="https://dicionario.priberam.org/muquirana" rel="noopener noreferrer"&gt;muquirana&lt;/a&gt;'). However, I later discovered the option to integrate the S3 bucket with a Content Delivery Network (CDN), a global network of servers that accelerates image loading times and caches results, reducing the need for frequent payments for image requests each time an email is opened. The use of a CDN can be particularly beneficial for optimizing costs and enhancing overall performance in your email image hosting strategy.&lt;/p&gt;

&lt;p&gt;Overall I found this to be the best approach, and it is also very useful when some style your UX team wants does not work on an e-mail provider (yes, i'm looking at you outlook desktop app, which for some reason is still used by a lot of people).&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In summary, emails present unique challenges and limitations, yet they can still exude aesthetic appeal. Now, while I made some jokes about irritating your UX team, it is imperative that you as a developer can effectively communicate these restrictions to them so that they can best create a design that is beautiful and possible to make. This collaborative approach ensures a balance between aesthetics and functionality. Explore &lt;a href="https://mosaico.io/" rel="noopener noreferrer"&gt;https://mosaico.io/&lt;/a&gt; for valuable, free insights into crafting impressive email designs!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>email</category>
      <category>html</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
