<?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: Jam3</title>
    <description>The latest articles on DEV Community by Jam3 (@jam3).</description>
    <link>https://dev.to/jam3</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%2Forganization%2Fprofile_image%2F493%2Fbd9a8314-3391-4ed0-b261-04ac13618ed8.jpg</url>
      <title>DEV Community: Jam3</title>
      <link>https://dev.to/jam3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jam3"/>
    <language>en</language>
    <item>
      <title>Modular Solution for Typeahead Feature</title>
      <dc:creator>Jeff Ong</dc:creator>
      <pubDate>Thu, 10 Dec 2020 20:53:35 +0000</pubDate>
      <link>https://dev.to/jam3/modular-solution-for-typeahead-feature-3mh3</link>
      <guid>https://dev.to/jam3/modular-solution-for-typeahead-feature-3mh3</guid>
      <description>&lt;p&gt;The feature that I am trying to build here is about providing a typeahead (or autocomplete) feature with a dynamic list of terms for a UI. Those terms could be words, phrases, or numbers. I am hoping to do the search operations without any HTTP requests. There are a few solutions available out there but most of them are too complex or too tightly coupled with the UI component.&lt;/p&gt;

&lt;p&gt;The solution has to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decoupled from the UI&lt;/li&gt;
&lt;li&gt;Efficient at search operations&lt;/li&gt;
&lt;li&gt;Flexible enough that a developer can implement it either on the front-end or back-end&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Planning
&lt;/h2&gt;

&lt;p&gt;Instead of breaking the search queries (prefixes) into arrays and compare it against every single word, phrase, and number in the form of an array, our solution will include a tree data structure and some recursive algorithms.&lt;/p&gt;

&lt;p&gt;So let us begin with Ternary Search Tree (TST). It is an extension of Tries Tree where each node contains a character and is connected by edges forming a tree structure; TST is more space-efficient as it only contains up to 3 child nodes whereas Tries will contain up to 26 child nodes in an alphabetical vocabulary dataset.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tries Tree - built with ['search', 'sections', 'seek', 'sear', 'seed', 'set', 'seo']&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zC_p3dmZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ywv571eqm6vun4dhot21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zC_p3dmZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ywv571eqm6vun4dhot21.png" alt="tries-visualization"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ternary Search Tree - built with ['search', 'sections', 'seek', 'sear', 'seed', 'set', 'seo']&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BA_Iw5zJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2fspn79ox0bniu3ybwza.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BA_Iw5zJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2fspn79ox0bniu3ybwza.png" alt="tst-visualization"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a TST data structure, child nodes are stored as Binary Search Tree (BST) where greater alphabets are stored on the right and others are on the left. This structure allows us to reduce the size of the problem by half at every iteration in a search operation with the time complexity of O(log n) on average.&lt;/p&gt;

&lt;p&gt;In a worse case scenario where the tree is not height-balanced meaning one side of the sub-tree has 2 or more edges than the other side, we have to hit every single node in a search operation until we reach the leaf node (the last node of the tree) or the matched suffix with the time complexity of O(n). We could prevent this by randomizing the data set and use the data item in the middle of the set as the root node. By doing this, we could guarantee that the tree will be height-balanced on average.&lt;/p&gt;

&lt;p&gt;Average Case:&lt;br&gt;
Search - O(log n)&lt;br&gt;
Insert - O(log n)&lt;/p&gt;

&lt;p&gt;Worst Case:&lt;br&gt;
Search - O(n)&lt;br&gt;
Insert - O(n)&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;After some planning, I have put together a &lt;a href="https://www.npmjs.com/package/typing-ahead"&gt;NPM package: typing-ahead&lt;/a&gt; implementing TST with the insert and search operations.&lt;/p&gt;

&lt;p&gt;Those operations are written as recursive algorithms where each function call branches off to create a stack of calls and each call, in our case, reduces the size of the problem by half. We could trace back to each step of the recursion to debug our operations. &lt;/p&gt;

&lt;p&gt;A set of tests (written with Jest) are also provided in the package to ensure that TST is height-balanced and the search operations return the expected results. One of the tests also validates the structure of the data model ensuring that it is a valid JSON schema.&lt;/p&gt;

&lt;p&gt;Here is how the package works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Import the module
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const typeahead = require('typing-ahead');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Supply the dataset and record the model
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const model = typeahead.generate([dataset]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Use the model when running search queries
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const results = typeahead.find('queries', model);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yfWKZB6x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ugwhi8izushubfd77ofv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yfWKZB6x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ugwhi8izushubfd77ofv.png" alt="typing-ahead package unit test"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;npm run test&lt;/code&gt; in typing-ahead package runs all available unit tests&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a sandbox that shows how it works.&lt;br&gt;
&lt;a href="https://codesandbox.io/embed/typing-ahead-pq64e?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark"&gt;https://codesandbox.io/embed/typing-ahead-pq64e?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark&lt;/a&gt;&lt;br&gt;
&lt;iframe src="https://codesandbox.io/embed/typing-ahead-pq64e"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;At the end of the day, we have a modular and fairly efficient solution for developers to implement either on the front-end layer of the applications or provide the search feature through remote services with caching. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D_Gm3DZE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4jd2x25f0ld5o15k8pf8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D_Gm3DZE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4jd2x25f0ld5o15k8pf8.gif" alt="typing-ahead-example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This prototype is built with &lt;a href="https://github.com/Jam3/nyg-nextjs"&gt;Jam3 NexJS Generator&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
    </item>
    <item>
      <title>Dynamic social image generation with CloudFront</title>
      <dc:creator>Iran Reyes Fleitas</dc:creator>
      <pubDate>Sat, 08 Aug 2020 16:32:03 +0000</pubDate>
      <link>https://dev.to/jam3/dynamic-social-image-generation-with-cloudfront-4bh5</link>
      <guid>https://dev.to/jam3/dynamic-social-image-generation-with-cloudfront-4bh5</guid>
      <description>&lt;p&gt;In this article, I will walk you through how to create dynamic images using Amazon CloudFront and AWS Lambda@Edge that will be shared on social networks. &lt;/p&gt;

&lt;p&gt;This solution is a simplified version of one of the modules from a project we did at &lt;a href="https://www.jam3.com/" rel="noopener noreferrer"&gt;Jam3&lt;/a&gt; some months ago. This project had strict performance requirements as it reached vast audiences from every continent. We had fun crafting the solution, and I hope you'll find it useful.&lt;/p&gt;



&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;There are many ways to achieve this result. After analyzing the pros and cons of all of them, we decided to move forward with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/cloudfront/" rel="noopener noreferrer"&gt;CloudFront&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/lambda/edge/" rel="noopener noreferrer"&gt;Lambda@Edge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;Our goal is to generate images that don't exist on the fly, and images that do exist will be cached and returned. Subsequence visits will get the CDN cached version until it expires.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer:&lt;br&gt;
We will use the dummy domains app.com and social.app.com as examples, but those are not real domains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Use case - First time generation&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Image &lt;a href="https://social.app.com/12-34-10.jpg" rel="noopener noreferrer"&gt;https://social.app.com/12-34-10.jpg&lt;/a&gt; is requested for the very first time. &lt;/li&gt;
&lt;li&gt;The cloud backend will generate that image on the fly and will return it as fast as possible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Use case - Cached version&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Image &lt;a href="https://social.app.com/12-34-10.jpg" rel="noopener noreferrer"&gt;https://social.app.com/12-34-10.jpg&lt;/a&gt; is requested again in less than x number of days.&lt;/li&gt;
&lt;li&gt;The image will be returned from the cache of Amazon CloudFront.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Use case - Cache expired version&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Image &lt;a href="https://social.app.com/12-34-10.jpg" rel="noopener noreferrer"&gt;https://social.app.com/12-34-10.jpg&lt;/a&gt; is requested again after x number of days.&lt;/li&gt;
&lt;li&gt;The cached version of the image already expired, a new image will be cache and returned.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These images will be used in the &lt;code&gt;og-image&lt;/code&gt; tag of a site, and when the site is shared on social networks with a dynamic URL query string, the right image will be rendered.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
When the site &lt;a href="https://app.com?social=12-34-10" rel="noopener noreferrer"&gt;https://app.com?social=12-34-10&lt;/a&gt; is shared on Facebook, the image &lt;a href="https://social.app.com/12-34-10.jpg" rel="noopener noreferrer"&gt;https://social.app.com/12-34-10.jpg&lt;/a&gt; will be fetched.&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;There are three (dummy) domains working here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The main application domain: &lt;a href="https://app.com" rel="noopener noreferrer"&gt;https://app.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The dynamic image generation service: &lt;a href="https://social.app.com" rel="noopener noreferrer"&gt;https://social.app.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A secret domain, only accessible by social.app.com, with an excellent performance site that will be used to generate the dynamic image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the main application (app.com) loads, we will dynamically build the URL in the &lt;code&gt;og-image&lt;/code&gt; tag based on its query parameters. If the page is shared on social networks, like Facebook, the social network crawler will fetch the dynamic image. If the dynamic image is cached or was already generated, it will be returned immediately. In case it doesn't exist it will be generated, cached, and returned.&lt;/p&gt;

&lt;p&gt;To generate the image, we created a high-performance site in which the markup was precisely the image we wanted to share. We loaded that site on AWS Lambda@Edge and, with Puppeteer, we took a screenshot that would be returned to the user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flfvtkkfaii0lzpxyv2dt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flfvtkkfaii0lzpxyv2dt.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We tried other approaches like compositing the image together using image libraries. But after many tests, the generation time on our approach was considerably less than the rest.&lt;/p&gt;
&lt;h3&gt;
  
  
  In-depth explanation
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Updating og-image
&lt;/h4&gt;

&lt;p&gt;When the user lands on &lt;a href="https://app.com?social=12-34-10" rel="noopener noreferrer"&gt;https://app.com?social=12-34-10&lt;/a&gt; we verify that the query string social has the expected format and, if it's valid, we update the site &lt;code&gt;og-image&lt;/code&gt; metadata with the value &lt;a href="https://social.app.com/12-34-10.jpg" rel="noopener noreferrer"&gt;https://social.app.com/12-34-10.jpg&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;If the &lt;code&gt;social&lt;/code&gt; query string doesn't exist or the value is incorrect, a default image will be used. This logic must be coded and tested carefully to avoid any attempt of reflected XSS.&lt;/p&gt;
&lt;h4&gt;
  
  
  Image generation service AWS infrastructure
&lt;/h4&gt;

&lt;p&gt;To build the dynamic image on the site hosted at &lt;a href="https://social.app.com" rel="noopener noreferrer"&gt;https://social.app.com&lt;/a&gt;, we will use some Amazon Web Services including Amazon Route53, Amazon S3, Amazon CloudFront, and &lt;a href="mailto:Lambda@Edge"&gt;Lambda@Edge&lt;/a&gt;. An extended production-ready application will also use AWS WAF, Amazon CloudWatch, Amazon SNS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjk79wqrrzve5cdsv8yhh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjk79wqrrzve5cdsv8yhh.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To briefly describe the AWS infrastructure, Route 53 will serve a CDN version of the site using CloudFront. Lambda@Edge will render the dynamic images and save them in an Image S3 bucket. The CloudFront distribution has a Web Firewall with some custom and out of the box rules, and everything is being logged to CloudWatch. If the Lambda@Edge fails the rendering or another suspicious activity/error is detected, CloudWatch will trigger alarms that will be delivered to the development team through Amazon Simple Notification Service (SNS).&lt;/p&gt;

&lt;p&gt;We will use two lambdas at the edge of the CloudFront distribution. The first Lambda will validate that the URL path is what we are expecting; otherwise, it will return a default image avoiding any 404. The validation Lambda@Edge will be executed before the request hits the CloudFront origin, on the Origin Request event. If the URL path is what we are expecting, we will let the request through toward the CloudFront origin, aka Amazon S3.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We are executing this Lambda after the CloudFront cache to improve performance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the CloudFront origin responds, it can come with an actual object or with a 404. If the image wasn't found, the second Lambda@Edge will be executed on the Origin Response event and it will generate the image dynamically, save it in Amazon S3, and return it to the cache.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9qfavcndmdqz2z7w345m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9qfavcndmdqz2z7w345m.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Lambda@Edge will save the image in the S3 bucket for future requests. When the CloudFront cache expires, the cache will be updated on the object stored in S3 and will be distributed available again.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  Image generation with puppeteer
&lt;/h4&gt;

&lt;p&gt;Although there are many ways to generate dynamic images, there isn't a one-size-fits-all solution/approach for all cases. In our case, the images to generate had many variations and crafting them with libraries like sharp or jimp, or a similar image manipulation library was not performing well; we also evaluated using a third-party service like Cloudinary, but the desired compositions were not possible.&lt;/p&gt;

&lt;p&gt;So our solution was to do all the magic in a mini React application and, with Puppeteer, take a screenshot. As you can imagine, our requirements for the React application were mainly hyper-performance. Using only hooks, brotli, webp, super lightweight dependencies, and other features, we were able to easily achieve the expected performance (loading times of 150-200ms).&lt;/p&gt;

&lt;p&gt;We are not going to cover how to protect the third secret domain, but you can use basic-auth or tokens. Because the performance is outstanding, I would recommend avoiding more heavy solutions like OAuth v2 or JWT.&lt;br&gt;
If you can afford a different domain for this third site, that would avoid any discovery using a DNS scanning tool for subdomains.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Let's dive into some code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Heads up:&lt;br&gt;
I'll omit some advanced details that will be covered at the end of this article.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Validation Lambda
&lt;/h3&gt;

&lt;p&gt;The validation Lambda@Edge looks similar to:&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="s1"&gt;use strict&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;isPathInvalid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./is-path-invalid&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;defaultPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/default.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&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;event&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;callback&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;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cf&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isPathInvalid&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;uri&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;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invalid path, redirecting to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;defaultPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defaultPath&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="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;Valid path, moving request to the origin&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="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, there's some logic that validates the URL path. If the URL path has the expected format, the request will continue to the next Lambda. In case it's invalid, it will change the requested image for the default image. &lt;/p&gt;

&lt;p&gt;The module &lt;code&gt;isPathInvalid&lt;/code&gt; depends on your validation. If it's too simple, you might want to consider writing the validation directly instead of having it on a separate file. In our case, it's better to have it on another module because we can write unit tests over the &lt;code&gt;isPathInvalid&lt;/code&gt; function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Main Lambda
&lt;/h3&gt;

&lt;p&gt;The main Lambda@Edge which generates and uploads assets to Amazon S3 looks similar to:&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="s1"&gt;use strict&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&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;generateImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./generate-image&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;uploadImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./upload-image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&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;event&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;callback&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&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;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cf&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;globaErrorStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&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;urlPath&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="nf"&gt;parse&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;uri&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;s3key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&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;imageId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;imageBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageId&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;imageBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;uploadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s3key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imageBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bodyEncoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;response&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The image generation and the default is invalid, letting the request to fail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;globaErrorStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`General exception, something failed on the way: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;globaErrorStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;globaErrorStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;We couldn't retrieve the asset&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="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&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 following steps explain what this function is doing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step #1&lt;/strong&gt;&lt;br&gt;
It's executing only if the image was not found in origin.&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step #2&lt;/strong&gt;&lt;br&gt;
Assuming the URL Path is coming as we are expecting, because of the validation Lambda@Edge, we are parsing the URL Path we need.&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;urlPath&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="nf"&gt;parse&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;uri&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;s3key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&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;imageId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a path like &lt;code&gt;12-23-12.jpg&lt;/code&gt;, &lt;code&gt;s3key&lt;/code&gt; will be &lt;code&gt;12-23-12.jpg&lt;/code&gt;, and &lt;code&gt;imageId&lt;/code&gt; will be &lt;code&gt;12-23-12&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step #3&lt;/strong&gt;&lt;br&gt;
We generate a dynamic image and return a buffer with the raw image (we'll cover this functionality later on).&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;imageBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step #4&lt;/strong&gt;&lt;br&gt;
If the image was successfully generated we proceed to upload it to S3.&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;uploadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s3key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step #5&lt;/strong&gt;&lt;br&gt;
Once the image has been uploaded to S3, we prepare the CloudFront response for caching.&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imageBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bodyEncoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;response&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&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;&lt;strong&gt;Step #6&lt;/strong&gt;&lt;br&gt;
Unexpected error handling in case something outside of our edge cases goes sideways. In that case, we will be returning a 500 error to the client.&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;globaErrorStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;We couldn't retrieve the asset&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="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generation module
&lt;/h3&gt;

&lt;p&gt;In the main Lambda, we used the function &lt;code&gt;generateImage(imageId)&lt;/code&gt; to generate the dynamic image; we will go through our approach in this chapter.&lt;/p&gt;

&lt;p&gt;A simplified version of the module looks like:&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="s1"&gt;use strict&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;promises&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;chromium&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome-aws-lambda&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;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://sharing-secret-site.com/&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;defaultImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./assets/default.jpg&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;imageBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;defaultViewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;deviceScaleFactor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headless&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&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;targetPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?social=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;imageId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?token=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;pageResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;waitUntil&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="s1"&gt;networkidle0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&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;pageStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pageResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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;pageStatus&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;pageStatus&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is not loading`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;imageBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jpeg&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;imageBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;defaultImage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fsError&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Exception while returning the default image: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fsError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;finally&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;browser&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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="nx"&gt;imageBuffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generateImage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Highlights:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It's using &lt;code&gt;chrome-aws-lambda&lt;/code&gt; because headless chrome by default is bigger than the maximum size supported by Lambda.&lt;/li&gt;
&lt;li&gt;The sharing application will load only if the token is right. You can also add basic-auth to the headers.&lt;/li&gt;
&lt;li&gt;We will wait until the network is idle, usually after the 500ms average.&lt;/li&gt;
&lt;li&gt;We are taking the screenshot of the loaded site and returning the image buffer.&lt;/li&gt;
&lt;li&gt;If there is any kind of error generating the screenshot, we will return the default image.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Note:&lt;br&gt;
After some timing tests, it's better for us not to compress the screenshot before sending it, but it may be better to consider it for your case.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  S3 Uploader module
&lt;/h3&gt;

&lt;p&gt;Finally, this is what the module that sends the generated image to S3 looks like:&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="s1"&gt;use strict&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;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&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;S3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;signatureVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v4&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;BUCKET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxx-xxxxxxx-xxxx-xxxxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s3key&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="nx"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;CacheControl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max-age=31536000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s3key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;StorageClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STANDARD&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="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Exception while writing image to bucket&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;error&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uploadImage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Highlights:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We are setting a high cache in origin (and CloudFront will respect it).&lt;/li&gt;
&lt;li&gt;For the Lambda to be able to upload objects to S3, it will need permissions in the bucket.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;The solution works as expected, and we recommend it if you have a similar use case.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Costs reductions:&lt;br&gt;
If we want to reduce Lambda running costs and pre-populate the most expected images, we just need to upload them to S3.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Production-ready considerations
&lt;/h2&gt;

&lt;p&gt;We've covered the main components of the solution to simplify this post, but to make the solution production-ready, there are a couple of extra things to have in mind. We will briefly cover some of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Main site query string validation &amp;amp; sanitization
&lt;/h3&gt;

&lt;p&gt;The main site responsibility is to move the query string to the &lt;code&gt;og-image&lt;/code&gt; and build the dynamic social image URL.&lt;/p&gt;

&lt;p&gt;Requesting &lt;code&gt;https://app.com?social=12-34-10&lt;/code&gt; will render&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;head&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://social.app.com/12-34-10.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We strongly recommend avoiding passing any kind of domain in the query string. If you need to pass it, make sure you whitelist it to avoid "URL Redirection to Untrusted Site" vulnerabilities.&lt;br&gt;
In our example, the social id has a specific format (like 12-34-10). We strongly recommend to sanitize it and verify that social id before update the &lt;code&gt;og-image&lt;/code&gt; tag, and it will avoid "Reflected XSS" vulnerabilities.&lt;br&gt;
And last but not least, if you use a regex to verify your social id, be aware of "ReDoS" vulnerabilities and make sure your regex is safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloudfront best practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use All Edge Locations for better performance.&lt;/li&gt;
&lt;li&gt;Make sure you're using one SSL certificate per domain instead of wildcard SSL certificates.&lt;/li&gt;
&lt;li&gt;Enable WAF (we will cover it later).&lt;/li&gt;
&lt;li&gt;Use the latest version of TLS.&lt;/li&gt;
&lt;li&gt;Enable Logging.&lt;/li&gt;
&lt;li&gt;Enable IPv6.&lt;/li&gt;
&lt;li&gt;Handle only HTTPS connections for the social and private domains, and Redirect HTTP to HTTPS for the main application.&lt;/li&gt;
&lt;li&gt;Allow only GET and HEAD requests.&lt;/li&gt;
&lt;li&gt;Cache as much as you can (there are multiple options).&lt;/li&gt;
&lt;li&gt;Disable any type of compression (we are returning an image).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Route53
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Setup query logging for your Hosted Zone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Web Application Firewall
&lt;/h3&gt;

&lt;p&gt;Everything outside of what we are expecting is an attempt to misuse the service. It's up to us how much we want other people to play with the service.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable WAF for CloudFront.&lt;/li&gt;
&lt;li&gt;Enable logging with Kinesis Firehose.&lt;/li&gt;
&lt;li&gt;Consider some managed rules like (IP reputation list or Known bad inputs).&lt;/li&gt;
&lt;li&gt;Consider creating rules to block unexpected query strings.&lt;/li&gt;
&lt;li&gt;Consider validating the URI path using a regex match.&lt;/li&gt;
&lt;li&gt;Consider restricting the size of the URI path.&lt;/li&gt;
&lt;li&gt;Consider blocking any request with a body (we are expecting only HTTP GET requests).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  S3 best practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The buckets should be private.&lt;/li&gt;
&lt;li&gt;Enable versioning (you shouldn't expect different versions of the images).&lt;/li&gt;
&lt;li&gt;Enable Cloudtrail for writing events.&lt;/li&gt;
&lt;li&gt;Enable server access logging (on another private bucket).&lt;/li&gt;
&lt;li&gt;Enable Server-Side encryption, KMS is recommended.&lt;/li&gt;
&lt;li&gt;If you are not expecting to delete the stored images, you can enable "Object Lock" when creating the bucket to increase the security around the bucket.&lt;/li&gt;
&lt;li&gt;Enabling "Transfer acceleration" will give you a better performance uploading the screenshots to S3 with an additional cost.&lt;/li&gt;
&lt;li&gt;If Object Lock was not used, add an event when objects are deleted and receive a notification.&lt;/li&gt;
&lt;li&gt;If you want to get isolated notifications per image created, you can create an event (if you enabled Cloudtrail, you will have this information).&lt;/li&gt;
&lt;li&gt;Make sure to achieve the Least Privilege Permissions and be specific with your bucket policies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CloudWatch
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Consider adding CloudFront alarm when requests are more than the expected.&lt;/li&gt;
&lt;li&gt;Consider adding CloudFront alarm when 4xx or 5xx happens.&lt;/li&gt;
&lt;li&gt;Consider adding alarms for both Lambda@Edge based on Errors and Duration.&lt;/li&gt;
&lt;li&gt;Consider adding alarms for both Lambda@Edge based on the content of the logs using Metric Filters.&lt;/li&gt;
&lt;li&gt;Create a dashboard with a collection of metrics of your interests (CloudFront and Lambdas).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Make sure your code is unit test friendly, and your test coverage covers your critical flows.&lt;/li&gt;
&lt;li&gt;Make sure to create integration tests that test all the functionality from end to end, with the AWS UI built-in test runner or better off AWS with lambda-local or serverless.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Infrastructure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;High recommended to use a code repository (GitHub, Gitlab, Bitbucket, etc.) and keep the source code there.&lt;/li&gt;
&lt;li&gt;High recommended having everything automated using a CI/CD platform.&lt;/li&gt;
&lt;li&gt;Infrastructure as Code is essential to avoid differences between environments and keep track of the changes, tune your skills with CloudFormation, Terraform, or Serverless.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you enjoyed reading this post and, most importantly, I hope it helps you.&lt;/p&gt;

</description>
      <category>cloudfront</category>
      <category>dynamic</category>
      <category>social</category>
      <category>aws</category>
    </item>
    <item>
      <title>Creating a localized experience for visitors from other countries using React Redux</title>
      <dc:creator>Vadim Namniak</dc:creator>
      <pubDate>Tue, 15 Oct 2019 21:02:34 +0000</pubDate>
      <link>https://dev.to/jam3/creating-a-localized-experience-for-visitors-from-other-countries-using-react-redux-6gk</link>
      <guid>https://dev.to/jam3/creating-a-localized-experience-for-visitors-from-other-countries-using-react-redux-6gk</guid>
      <description>&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;It is assumed you are already familiar with both React and Redux and looking to add internalization to your application. If you are not, there is a number of boilerplate options out there that can help you get started. &lt;br&gt;
Feel free to check out &lt;a href="https://github.com/Jam3/nyg-jam3"&gt;our implementation&lt;/a&gt; of it that we use at &lt;a href="https://www.jam3.com/"&gt;Jam3&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;You are highly advised to read the &lt;a href="https://www.i18next.com"&gt;i18next&lt;/a&gt; internationalization framework documentation to get an understanding of the main &lt;a href="https://www.i18next.com/#complete-solution"&gt;concepts&lt;/a&gt; and benefits of using it.&lt;/p&gt;

&lt;p&gt;List of required extra dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/i18next"&gt;i18next&lt;/a&gt; (37kB / 10.5kB)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/react-i18next/v/9.0.10"&gt;react-i18next v.9&lt;/a&gt; (12.4kB / 4.6kB)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/i18next-browser-languagedetector"&gt;i18next-browser-languagedetector&lt;/a&gt; (6kB / 2kB)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/i18next-redux-languagedetector"&gt;i18next-redux-languagedetector&lt;/a&gt; (2.2kB / 790B)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/i18next-chained-backend"&gt;i18next-chained-backend&lt;/a&gt; (2.2kB / 933B)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/i18next-fetch-backend"&gt;i18next-fetch-backend&lt;/a&gt; (4.3kB / 1.7kB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Take a sneak peek at these libraries before we proceed.&lt;/p&gt;

&lt;p&gt;👉 &lt;em&gt;Consider the overall additional cost of roughly 20kB (minified and gzipped) added to the production build&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Run this command in your terminal to install the above modules in one batch:&lt;br&gt;
&lt;code&gt;$ npm i --save i18next react-i18next@9.0.10 i18next-fetch-backend i18next-browser-languagedetector i18next-redux-languagedetector i18next-chained-backend&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;The example we’ll be referring to is bootstrapped with &lt;a href="https://github.com/facebook/create-react-app"&gt;Create React App&lt;/a&gt; with added Redux on top.&lt;br&gt;
Here’s what our application structure will look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VtI5Kd6T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/hx7k81886fgzpzb6gqwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VtI5Kd6T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/hx7k81886fgzpzb6gqwx.png" alt="App structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://codesandbox.io/s/github/Jam3/react-redux-i18n"&gt;CodeSandbox example&lt;/a&gt; or check this &lt;a href="https://github.com/Jam3/react-redux-i18n"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Creating translation files
&lt;/h3&gt;

&lt;p&gt;We are going to use English and Russian translations as an example.&lt;br&gt;
Let’s create two JSON files with identical structure and keep them in their respective folders:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
 &lt;center&gt;&lt;code&gt;/public/locales/en-US/common.json&lt;/code&gt;&lt;/center&gt;
&lt;br&gt;
 &lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
 &lt;center&gt;&lt;code&gt;/public/locales/ru/common.json&lt;/code&gt;&lt;/center&gt;

&lt;p&gt;These files will serve as our translation resources that are automatically loaded based on the detected browser language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Creating the i18n config file
&lt;/h3&gt;

&lt;p&gt;Make sure to check the complete list of available &lt;a href="https://www.i18next.com/overview/configuration-options"&gt;i18next config options&lt;/a&gt;.&lt;br&gt;
This is our main localization config file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
 &lt;center&gt;&lt;code&gt;/src/i18n/index.js&lt;/code&gt;&lt;/center&gt;

&lt;ul&gt;
&lt;li&gt;First off, we need to add the &lt;code&gt;i18next-chained-backend&lt;/code&gt; plugin which allows chaining multiple backends. There are several &lt;a href="https://www.i18next.com/overview/plugins-and-utils#backends"&gt;backend types&lt;/a&gt; available for different purposes. We are using &lt;code&gt;fetch&lt;/code&gt; to load our translation resources.&lt;/li&gt;
&lt;li&gt;Then we are adding &lt;code&gt;Browser Language Detector&lt;/code&gt; (connected with Redux store through &lt;code&gt;Redux Language Detector&lt;/code&gt;) for automatic user language detection in the browser. Read more about &lt;a href="https://github.com/i18next/i18next-browser-languageDetector#introduction"&gt;the approach&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Next up, we use &lt;code&gt;reactI18nextModule&lt;/code&gt; to pass &lt;code&gt;i18n&lt;/code&gt; instance down to &lt;code&gt;react-i18next&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally, we initialize &lt;code&gt;i18next&lt;/code&gt; with basic config options.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 3: Adding i18next reducer to the store
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Redux Language Detector&lt;/code&gt; provides &lt;code&gt;i18nextReducer&lt;/code&gt; so you don’t need to implement your own reducers or actions for it — simply include it in your store:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
 &lt;center&gt;&lt;code&gt;/src/redux/index.js&lt;/code&gt;&lt;/center&gt;

&lt;p&gt;👉 &lt;em&gt;For your convenience, use Redux dev tools in dev environment and make sure you import &lt;code&gt;composeWithDevTools&lt;/code&gt; from &lt;code&gt;redux-devtools-extension/developmentOnly&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Creating the main app file
&lt;/h3&gt;

&lt;p&gt;There’s nothing specifically related to the internalization in this file.&lt;br&gt;
We simply set the routes for our pages in a standard way.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
 &lt;center&gt;&lt;code&gt;/src/app/index.js&lt;/code&gt;&lt;/center&gt;
&lt;h3&gt;
  
  
  Step 5: Initializing the app and adding &lt;a href="https://react.i18next.com/legacy-v9/i18nextprovider"&gt;I18nextProvider&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The provider is responsible for passing the &lt;code&gt;i18next&lt;/code&gt; instance down to &lt;a href="https://react.i18next.com/legacy-v9/withnamespaces"&gt;withNamespaces&lt;/a&gt; HOC or &lt;a href="https://react.i18next.com/legacy-v9/namespacesconsumer"&gt;NamespacesConsumer&lt;/a&gt; render prop.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
 &lt;center&gt;&lt;code&gt;/src/index.js&lt;/code&gt;&lt;/center&gt;

&lt;p&gt;We initialized our store and &lt;code&gt;i18n&lt;/code&gt; config file with the same options to keep both in sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Using translation keys
&lt;/h3&gt;

&lt;p&gt;We‘ll use &lt;a href="https://react.i18next.com/legacy-v9/withnamespaces"&gt;withNamespaces&lt;/a&gt; HOC that passes the &lt;a href="https://www.i18next.com/overview/api#t"&gt;&lt;em&gt;t&lt;/em&gt; function&lt;/a&gt; as a prop down to the component. We need to specify the namespace(s), and the copy is now accessible via object properties using &lt;em&gt;&lt;code&gt;t&lt;/code&gt;&lt;/em&gt; function: &lt;code&gt;t(‘homePage.title’)&lt;/code&gt;.&lt;br&gt;
Note, it is required to prepend the namespace when accessing the copy from multiple namespaces within one component e.g. &lt;code&gt;t('shared:banner.title')&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
 &lt;center&gt;&lt;code&gt;/src/pages/Home.js&lt;/code&gt;&lt;/center&gt;

&lt;p&gt;Alternatively, we could use &lt;a href="https://react.i18next.com/legacy-v9/namespacesconsumer"&gt;NamespacesConsumer&lt;/a&gt; component which would also give us access to the &lt;em&gt;&lt;code&gt;t&lt;/code&gt;&lt;/em&gt; function. We’ll cover it in the next step.&lt;/p&gt;

&lt;p&gt;👉 &lt;em&gt;You can test language detection by changing your default browser language. When using Chrome, go to &lt;code&gt;chrome://settings/languages&lt;/code&gt; and move the languages up and down in the list&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7 (Bonus part): Creating language switcher
&lt;/h3&gt;

&lt;p&gt;Ok, we’ve implemented language auto-detection and dynamic translation resources loading. Now it’s time to take it up a notch and create a component that allows users switching the language through user interface.&lt;br&gt;
Make sure to include this component in your app.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
 &lt;center&gt;&lt;code&gt;/src/components/LanguageSwitcher.js&lt;/code&gt;&lt;/center&gt;

&lt;p&gt;&lt;code&gt;NamespacesConsumer&lt;/code&gt; render prop provides access to the &lt;code&gt;i18n&lt;/code&gt; instance. Its &lt;code&gt;changeLanguage&lt;/code&gt; method can be used to change language globally. This will force the app to re-render and update the site with the translated content.&lt;/p&gt;

&lt;p&gt;🎉That’s a wrap!&lt;/p&gt;

&lt;h2&gt;
  
  
  Code examples
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://codesandbox.io/s/github/Jam3/react-redux-i18n"&gt;CodeSandbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Jam3/react-redux-i18n"&gt;GitHub example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related documentation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://react.i18next.com/"&gt;i18next&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react.i18next.com/legacy-v9/"&gt;React i18next (v.9)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/i18next-browser-languagedetector"&gt;i18next Browser Language Detector&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>redux</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Cryptography git commits</title>
      <dc:creator>Donghyuk (Jacob) Jang</dc:creator>
      <pubDate>Sun, 18 Aug 2019 04:39:31 +0000</pubDate>
      <link>https://dev.to/jam3/cryptography-git-commits-c3h</link>
      <guid>https://dev.to/jam3/cryptography-git-commits-c3h</guid>
      <description>&lt;p&gt;-&lt;/p&gt;

&lt;p&gt;Git is an industry-standard version control tool used in IT fields, so it has a mandatory tool and a skill to learn for developers. &lt;/p&gt;

&lt;p&gt;During the development stage, developers make hundreds / thousands of commits. However, it is not difficult to find &lt;strong&gt;unverified commits&lt;/strong&gt;, although it would be related to &lt;strong&gt;security flaws&lt;/strong&gt; and potentially impacts the &lt;strong&gt;vulnerability&lt;/strong&gt; of projects.&lt;/p&gt;

&lt;p&gt;This article will mainly focus on &lt;strong&gt;the importance of the signing(verified) commits&lt;/strong&gt;. As well, there is one practice on how to pretend someone else in Git repositories.&lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Table of Contents&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Brief about SSH key&lt;/li&gt;
&lt;li&gt;GPG key&lt;/li&gt;
&lt;li&gt;An example of a security flaw without using GPG key&lt;/li&gt;
&lt;li&gt;How to setup GPG key&lt;/li&gt;
&lt;li&gt;How to create signing commits&lt;/li&gt;
&lt;li&gt;Final thoughts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Brief about SSH&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ft112w61uke9qty2golnk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ft112w61uke9qty2golnk.png" alt="SSH-key added in Github"&gt;&lt;/a&gt;&lt;/p&gt;
SSH-key added in Github



&lt;p&gt;First of all, I would assume that you have already set up &lt;strong&gt;SSH-key&lt;/strong&gt; on your machine to authenticate to Github and have it registered with your Github account. If not, please read &lt;a href="https://help.github.com/en/articles/connecting-to-github-with-ssh" rel="noopener noreferrer"&gt;Connecting to GitHub with SSH&lt;/a&gt; and follow the steps. &lt;/p&gt;

&lt;p&gt;Setting up SSH-key means that you do not have to provide your Github username or password for all of your git activity. That is the main reason to set up an SSH-key. Does connecting to Github with an SSH-key mean all of your commits become secure? We will dig into this and talk more about the disadvantages of only using an SSH-key later in this article.&lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Then, what is GPG key?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgnupg.org%2Fshare%2Flogo-gnupg-light-purple-bg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgnupg.org%2Fshare%2Flogo-gnupg-light-purple-bg.png" alt="GnuPG logo"&gt;&lt;/a&gt;&lt;/p&gt;
GnuPG logo



&lt;p&gt;Gnu Privacy Guard(GPG) is a tool that allows users to integrate an additional security layer easily with other applications. In this case, it will be Git. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GnuPG is a complete and free implementation of the OpenPGP standard as defined by RFC4880 (also known as PGP). GnuPG allows you to encrypt and sign your data and communications; it features a versatile key management system, along with access modules for all kinds of public key directories. GnuPG, also known as GPG, is a command line tool with features for easy integration with other applications. A wealth of frontend applications and libraries are available. GnuPG also provides support for S/MIME and Secure Shell (ssh).&lt;/p&gt;

&lt;p&gt;Since its introduction in 1997, GnuPG is Free Software (meaning that it respects your freedom). It can be freely used, modified and distributed under the terms of the GNU General Public License .&lt;/p&gt;

&lt;p&gt;The current version of GnuPG is 2.2.17. See the download page for other maintained versions.&lt;/p&gt;

&lt;p&gt;Gpg4win is a Windows version of GnuPG featuring a context menu tool, a crypto manager, and an Outlook plugin to send and receive standard PGP/MIME mails. The current version of Gpg4win is 3.1.10.&lt;/p&gt;
&lt;/blockquote&gt;
The GNU Privacy Guard



&lt;p&gt;-&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What could happen if you do not use GPG key?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffb91psceqge48rtj287m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffb91psceqge48rtj287m.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
a costume under surveillance



&lt;p&gt;One very common severe issue is that someone can set up other developers' Github username and email in their local machine, and create commits which will appear as "the someone else"' commits in the Git repository. Of course, this activity needs to meet a certain condition. The command below shows how to set up a different Github username and email within your terminal.&lt;/p&gt;

&lt;p&gt;-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// setup username and email
$ git config --global user.name "No GPG"
$ git config --global user.email no.gpg@email.com

// verity username and email
$ git config --global user.name
&amp;gt; No GPG
$ git config --global user.email
&amp;gt; no.gpg@email.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I want to share with you an example of two different commits that I pushed in a test Git repository(&lt;a href="https://github.com/DonghyukJacobJang/signing-commits" rel="noopener noreferrer"&gt;signing commit&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;When I created this git repository, I made the initial push with a signed(verified) commit. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxatnsmsl2917tuz76eth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxatnsmsl2917tuz76eth.png" alt="Screenshot of git commit history"&gt;&lt;/a&gt;&lt;/p&gt;
Screenshot of git commit history



&lt;p&gt;And then, I asked a friend of mine (&lt;a class="mentioned-user" href="https://dev.to/alemesa"&gt;@alemesa&lt;/a&gt;) to follow the practice above and gave him my Github username and email. (These credentials for signing in can be found with very little effort.) He created a commit and pushed to the &lt;code&gt;master&lt;/code&gt; branch. Check out the results below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fd404zd1zleurd6eq4j28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fd404zd1zleurd6eq4j28.png" alt="Screenshot of git commit history"&gt;&lt;/a&gt;&lt;/p&gt;
Screenshot of git commit history



&lt;p&gt;The commit has appeared that it was created by me. However, there is no "verified" flag like the first initial commit. &lt;/p&gt;

&lt;p&gt;If &lt;code&gt;master&lt;/code&gt; branch is not protected like the testing Git repository, then anyone in the contributor list can push any code by pretending to be someone else. Please find details about how to protect branches &lt;a href="https://help.github.com/en/enterprise/2.15/admin/developer-workflow/configuring-protected-branches-and-required-status-checks" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;NOTE: If you want to test this activity, please send me a direct message that includes your Github username. I will add you into the collaborator list.&lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How to setup GPG key&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The references will guide you on how to setup GPG key in your workstation, and how to add them into your Github account.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://help.github.com/en/articles/generating-a-new-gpg-key" rel="noopener noreferrer"&gt;Setup GPG key Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@timmywil/sign-your-commits-on-github-with-gpg-566f07762a43" rel="noopener noreferrer"&gt;Sign your commits on Github with GPG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How to create signing commit(s)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Add file(s) before creating a commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// add a single file
$ git add [path file]

// add all file
$ git add .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The git command below is how to create a commit with or without your signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// create commit with a message without signing
$ git commit -m "commit message"

// add all file that you want to commit
$ git commit -S -m "commit message"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your signing commit(s) is created, the next step is to push to a git repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// push the commit
$ git push 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The screenshot below is an example of git signing commit in the test git repository(&lt;a href="https://github.com/DonghyukJacobJang/signing-commits" rel="noopener noreferrer"&gt;signing commit&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fu2xhsjza2qwnstd04mwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fu2xhsjza2qwnstd04mwx.png"&gt;&lt;/a&gt;&lt;/p&gt;
detail of signing commit



&lt;p&gt;-&lt;/p&gt;

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

&lt;p&gt;To sum this article up, using GPG-key will increase security and authenticity level by encrypting its data, and adding a person's signature. If you or your organization have considered these, the steps above will bring its advantages and ensure trust with your commit workflow. &lt;/p&gt;

&lt;p&gt;NOTE: this activity may not be suitable for all case. Please make sure using this for the right purpose.&lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://gnupg.org/" rel="noopener noreferrer"&gt;Gnu PG&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://help.github.com/en/articles/telling-git-about-your-signing-key" rel="noopener noreferrer"&gt;Telling Git about your signing key&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://softwareengineering.stackexchange.com/questions/212192/what-are-the-advantages-and-disadvantages-of-cryptographically-signing-commits-a" rel="noopener noreferrer"&gt;What are the advantages and disadvantages of cryptographically signing commits and tags in Git?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://security.stackexchange.com/questions/120706/why-would-i-sign-my-git-commits-with-a-gpg-key-when-i-already-use-an-ssh-key-to" rel="noopener noreferrer"&gt;Why would I sign my git commits with a GPG key when I already use an SSH key to authenticate myself when I push?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://help.github.com/en/articles/signing-commits" rel="noopener noreferrer"&gt;How to create signing commits&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://confluence.atlassian.com/sourcetreekb/setup-gpg-to-sign-commits-within-sourcetree-765397791.html" rel="noopener noreferrer"&gt;Setup gpg-key within SourceTree&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.gnupg.org/gph/en/manual.html" rel="noopener noreferrer"&gt;GnuPG manual&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Collaborators&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Alejandro Mesa (&lt;a class="mentioned-user" href="https://dev.to/alemesa"&gt;@alemesa&lt;/a&gt;) - &lt;a href="mailto:alejandro.suarez@jam3.com"&gt;alejandro.suarez@jam3.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Danny Paton - &lt;a href="mailto:danny.paton@jam3.com"&gt;danny.paton@jam3.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>security</category>
      <category>sshkey</category>
      <category>gpgkey</category>
    </item>
    <item>
      <title>Managing .env variables for provisional builds with Create React App</title>
      <dc:creator>Donghyuk (Jacob) Jang</dc:creator>
      <pubDate>Sun, 07 Apr 2019 21:41:41 +0000</pubDate>
      <link>https://dev.to/jam3/managing-env-variables-for-provisional-builds-h37</link>
      <guid>https://dev.to/jam3/managing-env-variables-for-provisional-builds-h37</guid>
      <description>&lt;p&gt;When developing web applications by using Create React App, developers get &lt;code&gt;NODE_ENV=development&lt;/code&gt; on their local environment and &lt;code&gt;NODE_ENV=production&lt;/code&gt; on the production build by default. And, modifying &lt;code&gt;NODE_ENV&lt;/code&gt; is forbidden. According to the Create React App, this is an intentional setting to protect the &lt;code&gt;production&lt;/code&gt; environment from a mistake/accident deploying.&lt;/p&gt;

&lt;p&gt;You will be able to see scripts like below in &lt;code&gt;package.json&lt;/code&gt; after creating your web app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// package.json
scripts: {
  "start": "react-scripts start", // `NODE_ENV` is equal to `development`.
  "build": "react-scripts build", // `NODE_ENV` is equal to `production`.
  ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you create or already have &lt;code&gt;.env.development&lt;/code&gt; and &lt;code&gt;.env.production&lt;/code&gt; in the root of your project, these files will be used for running each script. &lt;code&gt;npm start&lt;/code&gt; will pick up &lt;code&gt;.env.development&lt;/code&gt;, and &lt;code&gt;npm build&lt;/code&gt; will use environment variables in &lt;code&gt;.env.production&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;-&lt;br&gt;
&lt;strong&gt;What if you want to setup &lt;code&gt;.env.staging&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article will show you how to manage environment variables for provisional builds.&lt;/p&gt;

&lt;p&gt;Let's dive into that! Oh, if you do not have any experiences of CRA, please &lt;a href="https://facebook.github.io/create-react-app/docs/getting-started"&gt;Getting started&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Story&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Imagine that your project will have three separated provisional environments; &lt;code&gt;development&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt;, and &lt;code&gt;production&lt;/code&gt;. Each environment is using different API endpoints. In addition to that, the project may require a &lt;code&gt;REACT_APP_CUSTOM_NODE_ENV&lt;/code&gt;. This is because &lt;code&gt;NODE_ENV&lt;/code&gt; will always be &lt;code&gt;production&lt;/code&gt; for the build regardless.&lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Goal&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;.env.development&lt;/code&gt;, &lt;code&gt;.env.staging&lt;/code&gt;, and &lt;code&gt;.env.production&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configure environment viriables for each build.&lt;/li&gt;
&lt;li&gt;Modify scripts in &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Getting started&lt;/strong&gt;
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Thankfully, &lt;code&gt;dotenv&lt;/code&gt; comes out of box. Let's create &lt;code&gt;.env&lt;/code&gt; files under the root folder to manage environment variables. The files are &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.env.development&lt;/code&gt;, &lt;code&gt;.env.staging&lt;/code&gt;, and &lt;code&gt;.env.production&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.env&lt;/code&gt; - Keep all common/shared environment variable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.development&lt;/code&gt; - Variables are used for the local development &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.staging&lt;/code&gt; - Variables are used for the staging build&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.production&lt;/code&gt; - Variables are used for the production build&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#.env
REACT_APP_DOC_TITLE = "Document title"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//.env.developement
REACT_APP_API_ENDPOINT = "https://development-api.endpoint.com/"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#.env.staging

# NODE_ENV will always be set to "production" for a build
# more details at https://create-react-app.dev/docs/deployment/#customizing-environment-variables-for-arbitrary-build-environments
REACT_APP_CUSTOM_NODE_ENV = "staging"
REACT_APP_API_ENDPOINT = "https://staging-api.endpoint.com/"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#.env.production
REACT_APP_API_ENDPOINT = "https://api.endpoint.com/"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;NOTE: The prefix &lt;code&gt;REACT_APP_&lt;/code&gt; is required when creating custom environment variables. &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;.env.development&lt;/code&gt; and &lt;code&gt;.env.production&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;As a default behavior, those files will be served with no configuration. You do not even have to update scripts in &lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;.env.staging&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Here is the main focus of this post. To target &lt;code&gt;.env.staging&lt;/code&gt; file for the staging build, we need a library to achieve this.&lt;/p&gt;

&lt;p&gt;1- Let's install &lt;code&gt;env-cmd&lt;/code&gt;. This library will will help us on using/executing a selected environment file. &lt;a href="https://www.npmjs.com/package/env-cmd"&gt;See more detail&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// execute command below at the root of project
$ npm install env-cmd --save
or
$ yarn add env-cmd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;2- Add a script in &lt;code&gt;package.json&lt;/code&gt; like below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// package.json
scripts: {
  "start": "react-scripts start", // `NODE_ENV` is equal to `development`.
  "build": "react-scripts build", // `NODE_ENV` is equal to `production`.
  "build:staging": "env-cmd -f .env.staging react-scripts build", // `NODE_ENV` is equal to `production`.
  ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;3- Finally, test your &lt;code&gt;build:staging&lt;/code&gt; script. &lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;

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

&lt;p&gt;The intention of this technique is to use different custom environment variables for many provisional environments without ejecting the default CRA configs. &lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Resources&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://facebook.github.io/create-react-app/docs/deployment#customizing-environment-variables-for-arbitrary-build-environments"&gt;Customizing Environment Variables for Arbitrary Build Environments&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables"&gt;Adding Custom Environment Viriables&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.npmjs.com/package/dotenv"&gt;dotenv&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Special Thanks&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/foxbit19"&gt;@foxbit19&lt;/a&gt; - found &lt;code&gt;env-cmd&lt;/code&gt; version 8.x requries additional argument to link to the custom env file. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/1970smthin"&gt;@j6000&lt;/a&gt; - pointed out &lt;code&gt;NODE_ENV&lt;/code&gt; for the build will always be set to "production" regardless.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>dotenv</category>
      <category>reactappenv</category>
    </item>
    <item>
      <title>How to prevent XSS attacks when using dangerouslySetInnerHTML in React</title>
      <dc:creator>Donghyuk (Jacob) Jang</dc:creator>
      <pubDate>Mon, 04 Feb 2019 21:11:04 +0000</pubDate>
      <link>https://dev.to/jam3/how-to-prevent-xss-attacks-when-using-dangerouslysetinnerhtml-in-react-1464</link>
      <guid>https://dev.to/jam3/how-to-prevent-xss-attacks-when-using-dangerouslysetinnerhtml-in-react-1464</guid>
      <description>&lt;p&gt;&lt;a href="https://media.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%2Ft473h25wb25zgob55r5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ft473h25wb25zgob55r5a.png" alt="XSS hero image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article intends to show one of the techniques we use to mitigate cross-site scripting (XSS) attacks at &lt;a href="https://jam3.com" rel="noopener noreferrer"&gt;Jam3&lt;/a&gt;. These vulnerabilities may appear when &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; is wrongly used, and our goal is to detect it ahead of time and to clean up untrusted values.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Dangerously Set innerHTML&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;This feature is designed to present and insert DOM formatted content data into the frontend. The use of the feature is a bad practice, especially when dealing with user inputs and dynamic data. You must consider its vulnerabilities in order to prevent &lt;a href="https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)" rel="noopener noreferrer"&gt;XSS attack&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Easy" to make thing safe&lt;/strong&gt; is one of React philosophy. React is flexible and extendable which means that the bad practice can be turning into the best practice. Sanitizing props value is one obvious option and strongly recommended.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;XSS attacks&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Cross-site scripting (XSS) allows attackers(hackers) to inject malicious code into a website for other end-users. By doing this, attackers may have access to personal data, cookies, webcams, and even more. Read more about &lt;a href="https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)" rel="noopener noreferrer"&gt;Cross-site scripting&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Copy &lt;code&gt;https://placeimgxxx.com/320/320/any" onerror="alert('xss injection')&lt;/code&gt; and paste it in the input field in the xss injection example below:&lt;br&gt;
&lt;a href="https://codesandbox.io/s/k9vxk9ppyo?autoresize=1&amp;amp;moduleview=1" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodesandbox.io%2Fstatic%2Fimg%2Fplay-codesandbox.svg" alt="Edit k9vxk9ppyo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Preventing XSS&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This issue is not restricted to React; to learn how to prevent it in your web development OWASP has a good &lt;a href="https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet" rel="noopener noreferrer"&gt;prevention cheat sheet&lt;/a&gt;. One approach to prevent XSS attacks is to sanitize data. It can be done either on the server-side or the client-side; in this article, we will focus on the client-side solution.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Preventing XSS with dangerouslyInnerSetHTML&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Sanitizing content in the frontend when using &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; is always a good security practice, even with a trusted &lt;a href="https://en.wikipedia.org/wiki/Single_source_of_truth" rel="noopener noreferrer"&gt;source of truth&lt;/a&gt;. For example, another development team in charge of maintaining the project changes the source of truth without realizing how it could impact the site. A change like that may cause a critical XSS vulnerability.&lt;/p&gt;

&lt;p&gt;At Jam3 we avoid using &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; whenever possible. When it's required, we &lt;strong&gt;always&lt;/strong&gt; apply sanitization security layers on both the back-end and front-end. In addition, we created an &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; rule called &lt;code&gt;no-sanitizer-with-danger&lt;/code&gt; inside &lt;a href="https://github.com/Jam3/eslint-plugin-jam3" rel="noopener noreferrer"&gt;&lt;code&gt;eslint-plugin-jam3&lt;/code&gt;&lt;/a&gt; to detect improper use of &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;ESLint rule&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;I assume that you are already familiar with ESLint. If not, &lt;a href="https://eslint.org/docs/user-guide/getting-started" rel="noopener noreferrer"&gt;Get Started&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ npm i eslint eslint-plugin-jam3 -save-dev


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Extend &lt;code&gt;pluginsin&lt;/code&gt; the .eslintrc config file by adding &lt;code&gt;jam3&lt;/code&gt;. You can omit the eslint-plugin- prefix. Then, configure the rules by adding &lt;code&gt;jam3/no-sanitizer-with-danger&lt;/code&gt; to the rules. Note: error level 2 is recommended. With this option, exit code will be 1. error level 1 will give warning alert, but does not affect exit code. 0 means to turn the rule off. The plugin will check that the content passed to &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; is wrapped in this sanitizer function. The wrapper function name can be also be changed in the JSON file (&lt;code&gt;sanitizer&lt;/code&gt; is the default wrapper name).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;How to use it&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Here is an &lt;strong&gt;unsafe&lt;/strong&gt; way of using dangerouslySetInnerHTML.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.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%2Fddomsjut8oky9160bxyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fddomsjut8oky9160bxyl.png" alt="dangerouslySetInnerHTML without sanitizer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the rule is enabled, your code editor will alert the lack of a sanitizer in &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;. For the purpose of this article we use &lt;a href="https://github.com/cure53/DOMPurify" rel="noopener noreferrer"&gt;dompurify&lt;/a&gt;, you can find an extended list of available sanitizers at the end of the article.&lt;/p&gt;

&lt;p&gt;The sanitizer wrapper must have a name, for the purpose of this article we are creating &lt;code&gt;const sanitizer = dompurify.sanitize;&lt;/code&gt;. However, it is recommended to create a sanitization utility to abstract your chosen sanitizer.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h1&gt;
  
  
  &lt;strong&gt;Sanitizer libraries&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Our team has researched and tried many sanitizers and can recommend these 3 libraries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/cure53/DOMPurify" rel="noopener noreferrer"&gt;&lt;strong&gt;dompurify&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strip out all dirty HTML and returns clean HTML data
npm Weekly download 50k+&lt;/li&gt;
&lt;li&gt;40 contributors &lt;/li&gt;
&lt;li&gt;Earned 2800+ GitHub ⭐️&lt;/li&gt;
&lt;li&gt;5.6kB MINIFIED + GZIPPED&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://github.com/leizongmin/js-xss" rel="noopener noreferrer"&gt;&lt;strong&gt;xss&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Escape HTML entity characters to prevent the attack which occurs to transform non-readable content for the end users&lt;/li&gt;
&lt;li&gt;npm weekly download 30k+&lt;/li&gt;
&lt;li&gt;18 contributors&lt;/li&gt;
&lt;li&gt;Earned 2500+ github ⭐️&lt;/li&gt;
&lt;li&gt;5.3kB MINIFIED + GZIPPED&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://github.com/yahoo/xss-filters" rel="noopener noreferrer"&gt;&lt;strong&gt;xss-filters&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Escape HTML entity characters to prevent the attack which occurs to transform non-readable content for the end users&lt;/li&gt;
&lt;li&gt;npm weekly download 30k+&lt;/li&gt;
&lt;li&gt;5 contributors&lt;/li&gt;
&lt;li&gt;Earned 900+ github ⭐️&lt;/li&gt;
&lt;li&gt;2.1kB MINIFIED + GZIPPED&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h1&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;To sum up, finding the most suitable sanitizer library for your project is very important for security. You might want to have a look at GitHub stars, npm download numbers, and maintenance routines. The use of &lt;code&gt;no-sanitizer-with-danger&lt;/code&gt; in &lt;code&gt;eslint-plugin-jam3&lt;/code&gt; will be a great choice to help ensure all data is being properly purified and gain confidence that your project will be safe from XSS vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Please keep it in mind there is a performance disadvantage of sanitizing data in client-side. For example, sanitizing all data at once may slow down the initial load. To prevent this in large scale web applications, you can implement a "lazy-sanitizing" approach to sanitize on the fly.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Further reading and sources&lt;/strong&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;ESLint developer guide&lt;/li&gt;
&lt;li&gt;Creating an ESLint Plugin&lt;/li&gt;
&lt;li&gt;eslint-plugin-react&lt;/li&gt;
&lt;li&gt;eslint-plugin-jam3&lt;/li&gt;
&lt;li&gt;Cross-site scripting&lt;/li&gt;
&lt;li&gt;XSS attack cheat sheet&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Contributors&lt;/strong&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/DonghyukJacobJang" rel="noopener noreferrer"&gt;Donghyuk(Jacob) Jang&lt;/a&gt; / &lt;a href="//mailto:jacob.jang@jam3.com"&gt;jacob.jang@jam3.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/iranreyes" rel="noopener noreferrer"&gt;Iran Reyes&lt;/a&gt; / &lt;a href="//mailto:iran.reyes@jam3.com"&gt;iran.reyes@jam3.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Peter Altamirano / &lt;a href="mailto:peter.altamirano@jam3.com"&gt;peter.altamirano@jam3.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.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%2F2u6hysdns7wagk9lsy3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F2u6hysdns7wagk9lsy3q.png" alt="Jam3 logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Jam3 is a design and experience agency that partners with forward-thinking brands from around the world. To learn more, visit us at &lt;a href="https://jam3.com" rel="noopener noreferrer"&gt;jam3.com&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Article by Donghyuk (Jacob) Jang&lt;/p&gt;

</description>
      <category>react</category>
      <category>xss</category>
      <category>dangerous</category>
      <category>eslint</category>
    </item>
  </channel>
</rss>
