<?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>Forem: Boryamba</title>
    <description>The latest articles on Forem by Boryamba (@bnn1).</description>
    <link>https://forem.com/bnn1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F687221%2Fb2f32066-3934-43ae-b147-faf733e04585.png</url>
      <title>Forem: Boryamba</title>
      <link>https://forem.com/bnn1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bnn1"/>
    <language>en</language>
    <item>
      <title>Building a Chrome Extension Using React and Vite: Part 2 - State Management and Message Passing</title>
      <dc:creator>Boryamba</dc:creator>
      <pubDate>Mon, 03 Jul 2023 14:40:46 +0000</pubDate>
      <link>https://forem.com/bnn1/how-do-they-talk-to-each-other-2p9</link>
      <guid>https://forem.com/bnn1/how-do-they-talk-to-each-other-2p9</guid>
      <description>&lt;p&gt;Welcome back to our ongoing series on building a Chrome extension with React and Vite. In the first part of this tutorial series, we got started with our Chrome extension setup. If you've missed that, you can check it out &lt;a href="https://dev.to/bnn1/mise-en-place-31n5"&gt;here&lt;/a&gt;. This time, we will dive into the heart of our extension, state management, and understand how our extension's components interact with each other.&lt;/p&gt;

&lt;p&gt;Before we move forward, let's pause and clarify the purpose of state management in our extension. State management refers to how we store and manipulate a data source accessible across various parts of our application. It's a crucial aspect of most applications as it helps us control data flow and communication throughout our application. &lt;/p&gt;

&lt;p&gt;Our extension consists of three parts: the popup, the content script, and the background script. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The popup&lt;/strong&gt; is our control panel for the extension, allowing us to toggle its features on and off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The content script&lt;/strong&gt; is responsible for modifying the website's DOM by adding checkboxes, buttons, and other UI elements as necessary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The background script&lt;/strong&gt;, although not mentioned in the first part, serves as a centralized data management hub, acting as our single source of truth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may ask, "Why do we need a background script? Can't we directly connect the popup and content script to local storage changes?" Technically, you could. However, there are a few reasons why this isn't the best practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Performance:&lt;/strong&gt; Directly connecting multiple parts of your extension to the local storage can lead to performance issues. With a background script, we can centralize state management and efficiently communicate changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity:&lt;/strong&gt; Trying to manage and track changes across various parts of your extension can quickly get complicated. A background script simplifies state management and makes debugging easier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Practice:&lt;/strong&gt; Utilizing a background script for managing state in your extension is a common pattern in extension development. It adheres to the principles of unidirectional data flow, a core concept in React development. This pattern ensures data consistency and simplifies data flow throughout your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence:&lt;/strong&gt; The background script maintains its state as long as the browser is running, ensuring the extension state's persistence. It manages and responds to events from all parts of your extension consistently.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Understanding these points, it's clear that our background script plays a crucial role in coordinating state and communication between the popup and the content script. By treating the background script as our state management layer, we can keep our UI - the popup and the content script - focused on presentation and user interaction.&lt;/p&gt;

&lt;p&gt;Let's delve into the technical aspect of our blog post now. &lt;/p&gt;

&lt;p&gt;So, how do we establish communication between these parts? It's pretty straightforward. The background script needs to store the state. We can use various data structures to do this, such as an object, a map, a set, or others depending on your specific needs. For this tutorial, I decided to use Valtio, a simple state management library - not for any special reason, but simply to experiment with it.&lt;/p&gt;

&lt;p&gt;The background script needs to send the state to the requested party, as well as update the state upon request. That sounds like sending and receiving messages, doesn't it? &lt;/p&gt;

&lt;p&gt;Firstly, let's handle the state. Here are the steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a file &lt;code&gt;extensionState.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a state inside the file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;valtio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Actions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;GET_STATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SET_STATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;set-state&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;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Action&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;Actions&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;isBulkDeleteEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;isChatExportEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;isPromptSavingEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtensionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMessage&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="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SET_STATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&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;I also added a few types and an action to update the state.&lt;/p&gt;

&lt;p&gt;Next, onto background script. First, when the extension is first installed, we need to initialize our state in the local storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onInstalled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addListener&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="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good. Now we can persist the state between page reloads, and we can also access the state from other contexts as well. &lt;/p&gt;

&lt;p&gt;Then, we need to send the state to those who ask for it, as well as implement changes to the state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ExtensionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../state/extensionState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MessageWithoutPayload&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GET_STATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MessageWithPayload&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SET_STATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ExtensionState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;MessageWithoutPayload&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;MessageWithPayload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onInstalled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addListener&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="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GET_STATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SET_STATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we receive the &lt;code&gt;GET_STATE&lt;/code&gt; message, we respond with the local valtio state because it's in sync with the local storage.&lt;br&gt;
When we receive the &lt;code&gt;SET_STATE&lt;/code&gt; message, we mutate our local state and update the local storage.&lt;/p&gt;

&lt;p&gt;Background worker is now ready to share and update the state. How cool is that? &lt;/p&gt;

&lt;p&gt;How about consuming the state? For this we will create a &lt;code&gt;useExtensionState&lt;/code&gt; hook that will return the extension state. First, we get a snapshot of the state. Snapshot is a valtio's way to provide reactive state of our state (sorry for the tautology 😅):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSnapshot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;valtio&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;useExtensionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;extensionState&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;Cool. Now we have access to the extension state. But it is not the same state that exists in the background context. Or in the popup context. Or in the content script context. They all live their own independent life! So we need to sync the state. Let's use &lt;code&gt;useEffect&lt;/code&gt; to handle side effects of retrieving the state from the local storage, or rather asking the background script to get the state for us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../state/extensionState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSnapshot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;valtio&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;useExtensionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&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="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMessage&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="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GET_STATE&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;extensionState&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;Here, we're asking the background script to get us the current state of our application, and update the local state with the retrieved value. And now we have access to the actual state of our extension. I hear someone yell "Hoozah!" — not so fast. &lt;/p&gt;

&lt;p&gt;When the state changes, it won't be reflected in our local state. We can only receive the updated state whenever we refresh the page, which is not good, because we want REACTIVITY. &lt;/p&gt;

&lt;p&gt;I tried subscribing to the state changes using valtio's subscribe, and whenever the state changes send notification to all parties that the state is updated, but the content script can't listen to messages sent via &lt;code&gt;chrome.runtime.sendMessage&lt;/code&gt;, so I had two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the background script a bit more complicated, finding the active tab and sending message to that tab&lt;/li&gt;
&lt;li&gt;Subscribe to local storage changes inside the &lt;code&gt;useExtensionState&lt;/code&gt; hook&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second option looks easier to me so I went with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ExtensionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../state/extensionState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSnapshot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;valtio&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;useExtensionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&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="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMessage&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="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GET_STATE&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&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;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;changes&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="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StorageChange&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;newValue&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="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;ExtensionState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newValue&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;acc&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;as&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;state&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;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&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="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;extensionState&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 &lt;code&gt;changes&lt;/code&gt; is an object whose values are also an object with &lt;code&gt;oldValue&lt;/code&gt; and &lt;code&gt;newValue&lt;/code&gt;. Because of that, we can't directly assign our state these changes, so we have to do a little bit of data transformation. We take key-value pair from the object, and assign each key a new state value. We merge the result into our state, and that's it. We also add a cleanup function to remove unneeded listeners. &lt;/p&gt;

&lt;p&gt;Let's test this out!&lt;/p&gt;

&lt;p&gt;I changed the popup's &lt;code&gt;App&lt;/code&gt; component like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useExtensionState&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../hooks/useExtensionState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;updateState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../state/extensionState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&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;extensionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useExtensionState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;updateState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;isBulkDeleteEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;extensionState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isBulkDeleteEnabled&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="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;extensionState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isBulkDeleteEnabled&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever the button is clicked, it should toggle the &lt;code&gt;isBulkDeleteEnabled&lt;/code&gt; value from &lt;code&gt;true&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;, and vice versa. To know that it's working inside the button we render text depending on the state value.&lt;/p&gt;

&lt;p&gt;We do the same for content script with one exception - we don't want to toggle state from content script (though you can if you want), we just want to make sure it has up to date state on each change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useExtensionState&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../hooks/useExtensionState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&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;extensionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useExtensionState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;extensionState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isBulkDeleteEnabled&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ENABLED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DISABLED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, rebuild the extension &lt;code&gt;yarn build&lt;/code&gt;, and click the refresh button in the extensions tab. Open the &lt;code&gt;https://chat.openai.com/&lt;/code&gt;, toggle the extension by clicking its button and try clicking the button. In the popup, the &lt;code&gt;Disable&lt;/code&gt; text switches with &lt;code&gt;Enable&lt;/code&gt;, and in the content script (which is barely visible due to its black text color and transparent background) also changes its text. &lt;/p&gt;

&lt;p&gt;Now you can yell "Hoozah!".&lt;/p&gt;

&lt;p&gt;What's left is to develop a pure react application for both popup and the content script, and so I decided to conclude the tutorial on the extension development with React. &lt;/p&gt;

&lt;p&gt;If you want to follow along to the bitter (hopefully sweeter) end, please let me know in the comments and I will continue the series. &lt;/p&gt;

&lt;p&gt;Hope you enjoyed this series. See you next time 😊&lt;/p&gt;

</description>
      <category>react</category>
      <category>vite</category>
      <category>extensions</category>
    </item>
    <item>
      <title>Building a Chrome Extension Using React and Vite: Part 1 - Setting things up</title>
      <dc:creator>Boryamba</dc:creator>
      <pubDate>Mon, 03 Jul 2023 13:12:57 +0000</pubDate>
      <link>https://forem.com/bnn1/mise-en-place-31n5</link>
      <guid>https://forem.com/bnn1/mise-en-place-31n5</guid>
      <description>&lt;p&gt;Hello everyone 👋&lt;/p&gt;

&lt;p&gt;For a while, I've been toying with the idea of building an extension for the ChatGPT website. I wanted to enhance its functionality by allowing multiple chat deletions and adding other useful features. But every time I started the development process, I found myself hitting a wall, particularly around Vite configuration for extension bundling or communication between different extension contexts.&lt;/p&gt;

&lt;p&gt;After many attempts, I finally succeeded! And I thought I'd share my experience with you all ❤️. This is the first part in a series of blog posts where I will guide you through my process.&lt;/p&gt;

&lt;p&gt;In this initial part, we'll cover the setup process: initializing the project, creating the popup, content script and background worker, and incorporating them into our &lt;code&gt;manifest.json&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;The second part which shows how to connect different parts of the extension together is available &lt;a href="https://dev.to/bnn1/how-do-they-talk-to-each-other-2p9"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Let's start by setting up a Vite project. Use your favorite package manager to establish a fresh Vite project. Here, I'll be using yarn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn create vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the installation, I select &lt;code&gt;React -&amp;gt; Typescript with SWC&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Once done, I add a few Vite plugins in the root directory of the newly created project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; vite-tsconfig-paths @crxjs/vite-plugin@beta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;vite-tsconfig-paths&lt;/code&gt; plugin supports path aliases and &lt;code&gt;@crxjs/vite-plugin@beta&lt;/code&gt; is a library for bundling the extension. &lt;/p&gt;

&lt;p&gt;Next, we need to register these plugins in the &lt;code&gt;vite.config.ts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tsconfigPaths&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite-tsconfig-paths&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vitejs/plugin-react-swc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ManifestV3Export&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@crxjs/vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;manifestJson&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./manifest.json&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;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;manifestJson&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ManifestV3Export&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// https://vitejs.dev/config/&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tsconfigPaths&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;crx&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;manifest&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, I've imported the manifest file and asserted it to the &lt;code&gt;ManifestV3Export&lt;/code&gt; type. The &lt;code&gt;crx&lt;/code&gt; plugin's typings require this to function correctly 😅. A quick note - I needed to add the &lt;code&gt;manifest.json&lt;/code&gt; file to my &lt;code&gt;tsconfig.node.json&lt;/code&gt;'s &lt;code&gt;include&lt;/code&gt; field to avoid a resolution error.&lt;/p&gt;

&lt;p&gt;Now let's discuss the &lt;code&gt;manifest.json&lt;/code&gt; file. This file provides an overview of my extension: its name, description, permissions, as well as the locations of entry files for the popup, content script, and background worker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ChatGPT Extension"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Improve your ChatGPT experience with this extension! Bulk exports, bulk deletes, save prompts and more!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"activeTab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"cookies"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"declarativeContent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"scripting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"tabs"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host_permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://chat.openai.com/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"service_worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/background/background.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"persistent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content_scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://chat.openai.com/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/contentScript/main.tsx"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"run_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document_end"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, permissions like&lt;/p&gt;

&lt;p&gt;&lt;code&gt;activeTab&lt;/code&gt;, &lt;code&gt;cookies&lt;/code&gt;, and &lt;code&gt;tabs&lt;/code&gt; grant the extension access to various aspects of the browser's functionalities. For example, &lt;code&gt;activeTab&lt;/code&gt; allows the extension to interact with the current tab while &lt;code&gt;cookies&lt;/code&gt; and &lt;code&gt;tabs&lt;/code&gt; allow access to cookie data and tab details respectively. &lt;/p&gt;

&lt;p&gt;Next, we organize our application structure. The setup process has provided us with several files. Here's how we arrange them:&lt;/p&gt;

&lt;p&gt;1) I create a new &lt;code&gt;popup&lt;/code&gt; directory inside my &lt;code&gt;src&lt;/code&gt; folder, move there &lt;code&gt;main.tsx&lt;/code&gt; and &lt;code&gt;App.tsx&lt;/code&gt; files, and update the &lt;code&gt;index.html&lt;/code&gt; file to point to the new entry file location:&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;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/src/popup/main.tsx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The popup runs in its own context, independently from the background workers or content scripts. It doesn't have access to the DOM, which is why we'll be using a content script for that purpose.&lt;/p&gt;

&lt;p&gt;2) I duplicate the &lt;code&gt;popup&lt;/code&gt; directory, rename it to &lt;code&gt;contentScript&lt;/code&gt;, and within the &lt;code&gt;main.tsx&lt;/code&gt; script, create a new &lt;code&gt;div&lt;/code&gt; element to append it to the document's &lt;code&gt;body&lt;/code&gt;. Then, I render my application into the created &lt;code&gt;div&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./App.tsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../index.css&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;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chatgpt-content-script-root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach ensures: a) there is a root element to render our application into; b) we will not have conflicts with the website's existing DOM elements, ensuring our extension doesn't interfere with the website's functionality or vice versa.&lt;/p&gt;

&lt;p&gt;3) Lastly, we create a background script inside the &lt;code&gt;background&lt;/code&gt; folder. For now, let's insert a dummy &lt;code&gt;console.log&lt;/code&gt; just to ensure everything is working correctly.&lt;/p&gt;

&lt;p&gt;Now comes the exciting part: testing our setup:&lt;/p&gt;

&lt;p&gt;1) &lt;code&gt;yarn build&lt;/code&gt; - this command builds my extension into a &lt;code&gt;dist&lt;/code&gt; directory.&lt;br&gt;
2) Enable developer mode in Google Chrome extensions.&lt;br&gt;
3) Load the extension by clicking the &lt;code&gt;Load unpacked&lt;/code&gt; button and selecting the &lt;code&gt;dist&lt;/code&gt; directory.&lt;br&gt;
4) Open &lt;code&gt;https://chat.openai.com/&lt;/code&gt; - at this point, you should notice the website's user interface has been modified by a Vite counter. When you click the extension button, guess what pops up? 😏 The last thing we need to test is the background script. Right-click anywhere inside the extension popup and select &lt;code&gt;Inspect popup&lt;/code&gt;. Here, you should see your dummy console.log.&lt;/p&gt;

&lt;p&gt;Yay! Congratulations to us! 🎉 We've just created our first Google Chrome extension. It currently doesn't do much more than adjusting the website's layout, but it's just the beginning.&lt;/p&gt;

&lt;p&gt;In case you face any issues during setup, ensure to double-check your file structure and the content of your &lt;code&gt;manifest.json&lt;/code&gt; file. Most errors at this stage come from misconfigurations in these areas. &lt;/p&gt;

&lt;p&gt;Stay tuned for the next part where I'll set up communication between different contexts of our extension. &lt;/p&gt;

&lt;p&gt;In the meantime, if you've tried these steps and have feedback, or if you'd like me to explain anything in more detail, please leave a comment below. I'd love to hear from you!&lt;/p&gt;

</description>
      <category>vite</category>
      <category>react</category>
      <category>extensions</category>
    </item>
    <item>
      <title>Custom Formik components with Typescript and Chakra UI</title>
      <dc:creator>Boryamba</dc:creator>
      <pubDate>Fri, 27 Aug 2021 11:17:55 +0000</pubDate>
      <link>https://forem.com/bnn1/custom-formik-components-with-typescript-and-chakra-ui-3f3c</link>
      <guid>https://forem.com/bnn1/custom-formik-components-with-typescript-and-chakra-ui-3f3c</guid>
      <description>&lt;p&gt;Resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://formik.org/docs/tutorial"&gt;Formik tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/61089182/how-to-properly-use-usefield-hook-from-formik-in-typescript"&gt;useField Formik hook with Typescript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chakra-ui.com/docs/form/form-control#usage-with-form-libraries"&gt;Using Chakra UI with Formik&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/chakra-ui/chakra-ui/issues/4328"&gt;Prop 'id' did not match problem&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  foreword
&lt;/h4&gt;

&lt;p&gt;Hello everyone.  &lt;/p&gt;

&lt;p&gt;I will be using &lt;code&gt;create next-app&lt;/code&gt; throughout the post but everything in here is appliable to regular React (CRA) applications.&lt;/p&gt;

&lt;p&gt;I expect you to be familiar with Chakra UI and Formik&lt;/p&gt;

&lt;p&gt;Let's get started =)&lt;br&gt;&lt;br&gt;
 &lt;br&gt;&lt;br&gt;
Let's start with creating a new NextJS app:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;yarn create next-app MyNextApp --typescript&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Open created folder in your IDE, then install Formik and Chakra UI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4 formik
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we create a new folder &lt;code&gt;components&lt;/code&gt; and a file named &lt;code&gt;CustomInput.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Inside that file create a new Functional component named &lt;code&gt;CustomInput&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CustomInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FormControl&lt;/span&gt; &lt;span class="nx"&gt;isInvalid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;touched&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;meta&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="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FormLabel&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;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/FormLabel&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/FormControl&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this moment Typescript will yell at us saying it doesn't know what're the &lt;code&gt;label&lt;/code&gt; and &lt;code&gt;props&lt;/code&gt;, we need to explain to that silly what those things are. So we need an interface which can be written two ways: either by extending &lt;code&gt;JSX.IntrinsicElements['input']&lt;/code&gt; interface or by intersecting &lt;code&gt;FieldHookConfig&amp;lt;string&amp;gt;&lt;/code&gt; type:&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="c1"&gt;// intersecting&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ICustomFieldProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// FieldHookConfig accepts value type as an argument,&lt;/span&gt;
&lt;span class="c1"&gt;// it is 'string' for input elements&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CustomInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FieldHookConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;ICustomFieldProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; 
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&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="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// extending&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ICustomFieldProps&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IntrinsicElements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&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="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CustomInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ICustomFieldProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&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="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;Choose whatever way you like. &lt;/p&gt;

&lt;p&gt;Now let's create our form. Open &lt;code&gt;pages/index.tsx&lt;/code&gt; file, delete everything in between &lt;code&gt;div&lt;/code&gt; and replace &lt;code&gt;div&lt;/code&gt; with &lt;code&gt;Formik&lt;/code&gt; component.&lt;br&gt;&lt;br&gt;
Add some dummy &lt;code&gt;initialValue&lt;/code&gt; and &lt;code&gt;onSubmit&lt;/code&gt; to &lt;code&gt;Formik&lt;/code&gt; component, place &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;CustomInput /&amp;gt;&lt;/code&gt; inside &lt;code&gt;&amp;lt;Formik&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Formik&lt;/span&gt; &lt;span class="nx"&gt;initialValues&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&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="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CustomInput&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Submit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Formik&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ta-da. We now have a form built with Chakra UI and managed by Formik. &lt;/p&gt;

&lt;p&gt;But. If you're using NextJS and you open devtools console, you will see a big fat error:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;Warning: Prop `id` did not match.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;We can fix this by adding &lt;code&gt;id&lt;/code&gt; to our form elements explicitly. Inside &lt;code&gt;CustomInput.tsx&lt;/code&gt; add &lt;code&gt;id&lt;/code&gt; attribute to &lt;code&gt;Input&lt;/code&gt; and &lt;code&gt;FormLabel&lt;/code&gt; components and &lt;code&gt;htmlFor&lt;/code&gt; attribute to &lt;code&gt;FormLabel&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CustomInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IFormikFieldProps&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;FieldHookConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FormControl&lt;/span&gt; &lt;span class="nx"&gt;isInvalid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;touched&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;meta&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="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FormLabel&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;props&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="s2"&gt;-label`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;htmlFor&lt;/span&gt;&lt;span class="o"&gt;=&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;props&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="s2"&gt;-input`&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="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/FormLabel&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Input&lt;/span&gt; 
        &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;props&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="s2"&gt;-input`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/FormControl&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool. It's done. &lt;/p&gt;

&lt;h5&gt;
  
  
  notes:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;for select, you must pass &lt;code&gt;props.children&lt;/code&gt; to Chakra UI's &lt;code&gt;Select&lt;/code&gt; component explicitly:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Select&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;props&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="s2"&gt;-select`&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="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Select&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;you can add &lt;code&gt;FormErrorMessage&lt;/code&gt; component, too&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading =)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AWS Elastic Beanstalk: redirect http to https (and www to non-www)</title>
      <dc:creator>Boryamba</dc:creator>
      <pubDate>Mon, 16 Aug 2021 16:24:40 +0000</pubDate>
      <link>https://forem.com/bnn1/deploying-dockerized-nextjs-app-to-aws-elastic-beanstalk-part-4-setting-redirects-48h9</link>
      <guid>https://forem.com/bnn1/deploying-dockerized-nextjs-app-to-aws-elastic-beanstalk-part-4-setting-redirects-48h9</guid>
      <description>&lt;h5&gt;
  
  
  Table of content:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-beanstalk-with-custom-domain-3hpk"&gt;Create dockerized NextJS app&lt;/a&gt; - part 1&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-2-deployment-to-beanstalk-47pj"&gt;Deploy dockerized app to AWS EB&lt;/a&gt; - part 2&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-3-setting-custom-domain-45bm"&gt;Connect custom domain to our application&lt;/a&gt; - previous part&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hello everyone.&lt;/p&gt;

&lt;p&gt;In previous parts we &lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-beanstalk-with-custom-domain-3hpk"&gt;created dockerized NextJS app&lt;/a&gt;, &lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-2-deployment-to-beanstalk-47pj"&gt;deployed it to AWS EB&lt;/a&gt; and &lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-3-setting-custom-domain-45bm"&gt;connected our app to custom domain&lt;/a&gt;.   &lt;/p&gt;

&lt;p&gt;In this part we are going to set up redirects from http to https (for security reasons) and from www to non-www (for SEO reasons).&lt;/p&gt;

&lt;p&gt;Everything in here is within AWS Free tier.&lt;/p&gt;

&lt;p&gt;Also please pay attention that all links to AWS I provide use my region of choice, if you're using different AWS region make sure to change it before making any changes!   &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up redirects
&lt;/h2&gt;

&lt;p&gt;To set up redirects we need to open &lt;a href="https://eu-west-1.console.aws.amazon.com/ec2/v2/home?region=eu-west-1#LoadBalancers:sort=loadBalancerName" rel="noopener noreferrer"&gt;EC2 Load Balancers Console&lt;/a&gt;, select our balancer and open &lt;code&gt;Listeners&lt;/code&gt; tab. &lt;/p&gt;

&lt;p&gt;We will start with redirecting http to https:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In &lt;code&gt;HTTP&lt;/code&gt; listener row &lt;code&gt;Rules&lt;/code&gt; column click &lt;code&gt;View/edit rules&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Switch to &lt;code&gt;Edit rules&lt;/code&gt; mode - open Pen tab&lt;/li&gt;
&lt;li&gt;Click on Pen icon on the left of &lt;code&gt;HTTP 80: default action&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Delete default action in &lt;code&gt;THEN&lt;/code&gt; column &lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Add action&lt;/code&gt; -&amp;gt; &lt;code&gt;Redirect to&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enter port &lt;code&gt;443&lt;/code&gt; on the right of protocol dropdown and click check icon to save changes&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Update&lt;/code&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%2Fs1x0ov5t3o2x1e5ppzhw.png" alt="edit mode"&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%2Fxr7ein42pjgy62yyfiev.png" alt="save changes"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next we will redirect www to non-www:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Switch to &lt;code&gt;Add rules&lt;/code&gt; mode&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Insert rule&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IF&lt;/code&gt; column select &lt;code&gt;Host header&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Inside &lt;code&gt;is&lt;/code&gt; field enter &lt;code&gt;www.yourwebsite&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;THEN&lt;/code&gt; column click &lt;code&gt;Add action&lt;/code&gt; -&amp;gt; &lt;code&gt;Redirect to&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enter port 443 and save changes
&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%2Fla4ki6j0v9y83lfy28ko.png" alt="add mode"&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%2Fhowxdi284aqw7tx78cwx.png" alt="save changes"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also want to set redirection from www to non-www on https connections:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go back to Listeners tab&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;View/edit rules&lt;/code&gt; in HTTPS row&lt;/li&gt;
&lt;li&gt;Add new rule:

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;IF&lt;/code&gt; host header is &lt;code&gt;www.yourdomain&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;THEN&lt;/code&gt; redirect to&lt;/li&gt;
&lt;li&gt;Set port 443&lt;/li&gt;
&lt;li&gt;Change &lt;code&gt;Origin host, path, query&lt;/code&gt; to &lt;code&gt;Custom&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Replace value of &lt;code&gt;Host&lt;/code&gt; field with your website domain&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Save changes.
&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%2F11k4mzo7zmj1i2tei8ca.png" alt="add new https rule"&gt;
&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;You now have fully functional https website that is being served completely free of charge (within Free tier quota of course). &lt;/p&gt;

&lt;p&gt;Hope you liked the series. Bye-bye =)&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>aws</category>
      <category>docker</category>
      <category>beanstalk</category>
    </item>
    <item>
      <title>Adding custom domain for AWS Elastic Beanstalk application</title>
      <dc:creator>Boryamba</dc:creator>
      <pubDate>Mon, 16 Aug 2021 15:38:21 +0000</pubDate>
      <link>https://forem.com/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-3-setting-custom-domain-45bm</link>
      <guid>https://forem.com/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-3-setting-custom-domain-45bm</guid>
      <description>&lt;h5&gt;
  
  
  Table of content:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-beanstalk-with-custom-domain-3hpk"&gt;Create dockerized NextJS app&lt;/a&gt; - part 1&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-2-deployment-to-beanstalk-47pj"&gt;Deploy dockerized app to AWS EB&lt;/a&gt; - previous part&lt;/li&gt;
&lt;li&gt;Connect domain to Route53&lt;/li&gt;
&lt;li&gt;Set up SSL&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-elastic-beanstalk-part-4-setting-redirects-48h9"&gt;Set up redirects&lt;/a&gt; - next part&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hello everyone.&lt;/p&gt;

&lt;p&gt;In previous part we &lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-beanstalk-with-custom-domain-3hpk"&gt;created dockerized NextJS app&lt;/a&gt; and &lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-2-deployment-to-beanstalk-47pj"&gt;deployed it to AWS EB&lt;/a&gt;.   &lt;/p&gt;

&lt;p&gt;In this part we are going to connect custom domain to our Elastic Beanstalk application (to make website url fancy, like &lt;code&gt;mysuperwebsite.com&lt;/code&gt;), obtain SSL certificate for our website and set up secure https connection to our website!&lt;/p&gt;

&lt;p&gt;Everything in here is within AWS Free tier (except for domain, which can be bought for as cheap as $1).&lt;/p&gt;

&lt;p&gt;Also please pay attention that all links to AWS I provide use my region of choice, if you're using different AWS region make sure to change it before making any changes!   &lt;/p&gt;

&lt;p&gt;Lets get started =))  &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;h2&gt;
  
  
  Use custom domain for your application
&lt;/h2&gt;

&lt;p&gt;This step requires you to have, guess it, custom domain.  &lt;/p&gt;

&lt;p&gt;First thing we need to do is to create a new hosted zone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://console.aws.amazon.com/route53/v2/hostedzones#" rel="noopener noreferrer"&gt;Route53 console page&lt;/a&gt; and click &lt;code&gt;Create hosted zone&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fill in domain name (optionally description), in my case it is &lt;code&gt;borisnovikov.xyz&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select Type - &lt;code&gt;Public hosted zone&lt;/code&gt; and click &lt;code&gt;Create hosted zone&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we need to visit our domain registrar and change domain nameservers.&lt;br&gt;&lt;br&gt;
I'm using Namecheap but the process should be similar for every domain provider: place each value of NS record (from AWS Hosted zone) to Domain registrar nameservers&lt;br&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%2Fl5zw6wfaaozoq0py50v7.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%2Fl5zw6wfaaozoq0py50v7.png" alt="ns records"&gt;&lt;/a&gt;&lt;br&gt;
It can take up to 48 hours to register new nameservers. &lt;/p&gt;

&lt;p&gt;To access website using our domain we need to add two alias records of type A: one for our main domain (borisnovikov.xyz) and the other is for subdomain (www website version): &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On Hosted zone page click &lt;code&gt;Create record&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Switch &lt;code&gt;Alias&lt;/code&gt; toggler&lt;/li&gt;
&lt;li&gt;Record type - &lt;code&gt;A&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Route traffic to - Beanstalk, then select region and environment&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Create record&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same for www but add &lt;code&gt;www&lt;/code&gt; prefix to &lt;code&gt;Record name&lt;/code&gt; field.    &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%2Fuploads%2Farticles%2F0tlmc88rnkj048fcnwnf.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%2F0tlmc88rnkj048fcnwnf.png" alt="record configuration"&gt;&lt;/a&gt;   &lt;/p&gt;

&lt;p&gt;Wait a few minutes and voi la - our website is now accessible with our domain    &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up SSL
&lt;/h3&gt;

&lt;p&gt;To set up SSL we need to obtain SSL certificate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit &lt;a href="https://eu-west-1.console.aws.amazon.com/acm/home?region=eu-west-1#/firstrun/" rel="noopener noreferrer"&gt;AWS Certificate Manager&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;Under &lt;code&gt;Provision certificates&lt;/code&gt; click &lt;code&gt;Get started&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Request a certificate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add two domain names: one for your website and the other for www version
&lt;/li&gt;
&lt;/ol&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%2Fcdycf8mfxso3vazczpcx.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%2Fcdycf8mfxso3vazczpcx.png" alt="adding domain names"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;Here I added wildcard domain (*.borisnovikov.xyz) - it will cover all subdomains including www or any other.&lt;br&gt;&lt;br&gt;
Click &lt;code&gt;Next&lt;/code&gt;, select &lt;code&gt;DNS Validation&lt;/code&gt; and keep clicking &lt;code&gt;Next&lt;/code&gt; until &lt;code&gt;Validation&lt;/code&gt; step.  &lt;/p&gt;

&lt;p&gt;Because we delegated domain management to AWS by setting nameservers, we need to validate ownership by expanding any domain and clicking &lt;code&gt;Create record in Route 53&lt;/code&gt;. Click &lt;code&gt;Continue&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;SSL certificate will be issued after a while (it can take up to 48 hours - usually within 30 minutes after nameservers delegation is completed).   &lt;/p&gt;

&lt;h4&gt;
  
  
  Adding secure connection to our website
&lt;/h4&gt;

&lt;p&gt;Next step is to add https listener to load balancer.&lt;/p&gt;

&lt;p&gt;First we need to modify Load Balancer security group to allow secure connections:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://eu-west-1.console.aws.amazon.com/ec2/v2/home?region=eu-west-1" rel="noopener noreferrer"&gt;EC2 Dashboard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;In sidebar click Load Balancers&lt;/li&gt;
&lt;li&gt;Select your Load balancer and scroll down &lt;code&gt;Description&lt;/code&gt; tab until &lt;code&gt;Security&lt;/code&gt; section&lt;/li&gt;
&lt;li&gt;Copy security group name and open link near it
&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%2F751tdn46ur79eqeis0hc.png" alt="security group name"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the Security groups page search the page (Ctrl+F in most browsers) for security group name you copied earlier, click on Security group ID.&lt;/p&gt;

&lt;p&gt;On opened page click &lt;code&gt;Edit inbound rules&lt;/code&gt;, then click &lt;code&gt;Add rule&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;HTTPS&lt;/code&gt; Type, Source - &lt;code&gt;Anywhere IPv4&lt;/code&gt;, save rule and close the page.  &lt;/p&gt;

&lt;p&gt;Now we will set https listener:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On &lt;a href="https://eu-west-1.console.aws.amazon.com/ec2/v2/home?region=eu-west-1#LoadBalancers:sort=loadBalancerName" rel="noopener noreferrer"&gt;Load Balancers&lt;/a&gt; page select your load balancer&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Listeners&lt;/code&gt; tab and copy default rule for HTTP listener (forwarding to ...)&lt;/li&gt;
&lt;li&gt;Click button &lt;code&gt;Add listener&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;HTTPS&lt;/code&gt; protocol from the dropdown &lt;/li&gt;
&lt;li&gt;In &lt;code&gt;Default actions&lt;/code&gt; click &lt;code&gt;Add action&lt;/code&gt; -&amp;gt; &lt;code&gt;Forward to&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;From the &lt;code&gt;Target group&lt;/code&gt; dropdown select the same group you copied earlier from HTTP listener default action&lt;/li&gt;
&lt;li&gt;From &lt;code&gt;Security policy&lt;/code&gt; dropdown select suitable policy (default &lt;code&gt;ELBSecurityPolicy-2016-08&lt;/code&gt; policy is recommended). More about policies &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/network/create-tls-listener.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Default SSL Certificate&lt;/code&gt; you created earlier. &lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Add listener&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tada! You can now access your website via https protocol =)  &lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-elastic-beanstalk-part-4-setting-redirects-48h9"&gt;next part&lt;/a&gt; we are going to set up redirects from http to https, from www to non-&lt;a href="http://www" rel="noopener noreferrer"&gt;www&lt;/a&gt;. Hope to see you there =) &lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>beanstalk</category>
      <category>docker</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Deploying dockerized NextJS App to AWS Elastic Beanstalk</title>
      <dc:creator>Boryamba</dc:creator>
      <pubDate>Mon, 16 Aug 2021 14:16:04 +0000</pubDate>
      <link>https://forem.com/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-2-deployment-to-beanstalk-47pj</link>
      <guid>https://forem.com/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-2-deployment-to-beanstalk-47pj</guid>
      <description>&lt;h5&gt;
  
  
  Table of content:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-beanstalk-with-custom-domain-3hpk"&gt;Create dockerized NextJS app&lt;/a&gt; - previous part&lt;/li&gt;
&lt;li&gt;
Manual upload

&lt;ul&gt;
&lt;li&gt;New Beanstalk application&lt;/li&gt;
&lt;li&gt;Existing Beanstalk application&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Beanstalk CLI

&lt;ul&gt;
&lt;li&gt;Install EB CLI&lt;/li&gt;
&lt;li&gt;Initialize application&lt;/li&gt;
&lt;li&gt;Deploy&lt;/li&gt;
&lt;li&gt;Update&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-3-setting-custom-domain-45bm"&gt;Add custom domain&lt;/a&gt; - next part&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-elastic-beanstalk-part-4-setting-redirects-48h9"&gt;Set up redirects&lt;/a&gt; - part 4&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Hello everyone.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-beanstalk-with-custom-domain-3hpk"&gt;previous part&lt;/a&gt; we created dockerized NextJS app. Now it's time to deploy.&lt;br&gt;&lt;br&gt;
Everything in here is within AWS Free tier.&lt;/p&gt;

&lt;p&gt;Also please pay attention that all links to AWS I provide use my region of choice, if you're using different AWS region make sure to change it before making any changes!    &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Deployment to Beanstalk
&lt;/h2&gt;

&lt;p&gt;There are two ways to deploy our app to Beanstalk:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Manual upload to Beanstalk&lt;/li&gt;
&lt;li&gt;Beanstalk CLI
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;    &lt;/p&gt;
&lt;h3&gt;
  
  
  Manual upload to Beanstalk
&lt;/h3&gt;

&lt;p&gt;In your project folder delete &lt;code&gt;node_modules&lt;/code&gt; directory (and &lt;code&gt;.next&lt;/code&gt; if present), archive leftovers into zip archive. Switch to AWS Console.  &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;    &lt;/p&gt;
&lt;h4&gt;
  
  
  New Beanstalk application
&lt;/h4&gt;

&lt;p&gt;If you don't have a Beanstalk application, we need to create one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open AWS Console &lt;a href="https://eu-west-1.console.aws.amazon.com/elasticbeanstalk/home?region=eu-west-1#/welcome" rel="noopener noreferrer"&gt;Beanstalk welcome page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Create application&lt;/code&gt; button&lt;/li&gt;
&lt;li&gt;Fill in application name and select platform &lt;code&gt;Docker&lt;/code&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%2Fv2ri5gdtgkwh106mi38q.png" alt="application name and platform section configuration"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;code&gt;Application code&lt;/code&gt; section select &lt;code&gt;Upload your code&lt;/code&gt;, in &lt;code&gt;Source code origin&lt;/code&gt; select &lt;code&gt;Local file&lt;/code&gt; and choose created zip archive.&lt;br&gt;&lt;br&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%2Fwivf7tk921w9tyh86nix.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%2Fwivf7tk921w9tyh86nix.png" alt="application code section configuration"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;Finally, click &lt;code&gt;Create application&lt;/code&gt;. It will take a few minutes to deploy our app.  &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;     &lt;/p&gt;
&lt;h4&gt;
  
  
  Existing Beanstalk application
&lt;/h4&gt;

&lt;p&gt;If you already have a Beanstalk application, we need to create a new application for our NextJS app (skip to create new environment if you want to use existing Beanstalk application):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In EB Console jump to &lt;code&gt;Applications&lt;/code&gt; tab in the sidebar&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Create new application&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fill in application name and click &lt;code&gt;Create&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Now it's time to create a new environment for our Next app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open created application (if you was following previous step, you already there)&lt;/li&gt;
&lt;li&gt;Tap &lt;code&gt;Create a new environment&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;On &lt;code&gt;Select environment tier&lt;/code&gt; step leave defaults (&lt;code&gt;Web server environment&lt;/code&gt;) and click &lt;code&gt;Select&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Docker&lt;/code&gt; platform and upload your zip archive
&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%2Ff3v5iamjp602dz0i241z.png" alt="create environment step"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, click &lt;code&gt;Create environment&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;After it's done you'll be provided with a URL for your application.     &lt;/p&gt;

&lt;p&gt;To update application, open environment in EB console and click &lt;code&gt;Upload and deploy&lt;/code&gt;    &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;    &lt;/p&gt;
&lt;h3&gt;
  
  
  Elastic Beanstalk CLI
&lt;/h3&gt;

&lt;p&gt;First you need to install EB CLI.&lt;br&gt;&lt;br&gt;
Follow instructions &lt;a href="https://github.com/aws/aws-elastic-beanstalk-cli-setup" rel="noopener noreferrer"&gt;there&lt;/a&gt; to install it using script.  &lt;/p&gt;

&lt;p&gt;If for some reason it's not working (i.e. you're using Arch linux), follow the procedures &lt;a href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3-install-advanced.html#eb-cli3-install-advanced.install" rel="noopener noreferrer"&gt;there&lt;/a&gt;.        &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;    &lt;/p&gt;
&lt;h4&gt;
  
  
  Initialize application
&lt;/h4&gt;

&lt;p&gt;After it's installed and checked to work, we need to initialize our app:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;eb init -r eu-west-1 -p docker my-dockerized-next-app&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-r eu-west-1&lt;/code&gt; flag tells what region to use&lt;br&gt;
&lt;code&gt;-p docker&lt;/code&gt; flag tells that we're using docker&lt;br&gt;
&lt;code&gt;my-dockerized-next-app&lt;/code&gt; sets application name    &lt;/p&gt;

&lt;p&gt;If you don't specify region flag, use &lt;code&gt;eb status&lt;/code&gt; or check &lt;code&gt;config.yml&lt;/code&gt; file to find what region is used.   &lt;/p&gt;

&lt;p&gt;You can use application name that already exists in AWS EB Console - every environment you create with EB CLI will be created within existing EB application.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;    &lt;/p&gt;
&lt;h4&gt;
  
  
  Deploy
&lt;/h4&gt;

&lt;p&gt;Before proceeding, make sure all your files are commited - EB CLI will produce incorrect result if they are not.    &lt;/p&gt;

&lt;p&gt;Now we can deploy our app to AWS:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;eb create --elb-type application my-nextjs-env&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--elb-type application&lt;/code&gt; tells to use Application Load Balancer - without this flag Classic Load Balancer (which is outdated) will be used&lt;br&gt;
&lt;code&gt;my-nextjs-env&lt;/code&gt; sets environment name&lt;/p&gt;

&lt;p&gt;If environment with provided name already exists within selected application, it will be updated. &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;h4&gt;
  
  
  Update
&lt;/h4&gt;

&lt;p&gt;To update application, commit your changes and execute &lt;code&gt;eb deploy&lt;/code&gt; command.     &lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-3-setting-custom-domain-45bm"&gt;next part&lt;/a&gt; we are going to connect our domain to EB application. Hope to see you there =)&lt;/p&gt;

&lt;p&gt;Thanks for reading. &lt;/p&gt;

</description>
      <category>aws</category>
      <category>beanstalk</category>
      <category>nextjs</category>
      <category>docker</category>
    </item>
    <item>
      <title>Creating dockerized NextJS Application</title>
      <dc:creator>Boryamba</dc:creator>
      <pubDate>Mon, 16 Aug 2021 12:41:55 +0000</pubDate>
      <link>https://forem.com/bnn1/deploying-dockerized-nextjs-app-to-aws-beanstalk-with-custom-domain-3hpk</link>
      <guid>https://forem.com/bnn1/deploying-dockerized-nextjs-app-to-aws-beanstalk-with-custom-domain-3hpk</guid>
      <description>&lt;h5&gt;
  
  
  Resources:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.zack.computer/docker-containers-nodejs-nextjs"&gt;Create NextJS docker container&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Table of content:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Create NextJS app&lt;/li&gt;
&lt;li&gt;Dockerize NextJS app&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-2-deployment-to-beanstalk-47pj"&gt;Deploy dockerized app to AWS EB&lt;/a&gt; - next part&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-3-setting-custom-domain-45bm"&gt;Add custom domain&lt;/a&gt; - part 3&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-elastic-beanstalk-part-4-setting-redirects-48h9"&gt;Set up redirects&lt;/a&gt; - part 4&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hello everyone.&lt;/p&gt;

&lt;p&gt;I assume that you already have Docker, NodeJS and YARN installed.  &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create NextJS app
&lt;/h2&gt;

&lt;p&gt;To create a new Nextjs app execute &lt;code&gt;yarn create next-app app-name&lt;/code&gt; or you can pick an example app from &lt;a href="https://github.com/vercel/next.js/tree/canary/examples"&gt;there&lt;/a&gt; and create a new app with supplied command.&lt;br&gt;&lt;br&gt;
I'm going to pick &lt;a href="https://github.com/vercel/next.js/tree/canary/examples/blog-starter-typescript"&gt;blog starter example&lt;/a&gt;.&lt;br&gt;
In the &lt;code&gt;How to use&lt;/code&gt; section copy command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn create next-app --example blog-starter-typescript blog-starter-typescript-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;paste it in your terminal and hit &lt;code&gt;enter&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerize NextJS app
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://blog.zack.computer/docker-containers-nodejs-nextjs#final-dockerfile"&gt;this awesome post&lt;/a&gt; and copy the final version of docker configuration.&lt;br&gt;&lt;br&gt;
Inside the root folder of your project (where &lt;code&gt;package.json&lt;/code&gt; file is) create a new file named &lt;code&gt;Dockerfile&lt;/code&gt;, paste copied configuration into it.&lt;br&gt;&lt;br&gt;
We need to tweak it a little (otherwise AWS will complain - try out yourself without deleting if you're curious): &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;delete image aliases (&lt;code&gt;AS %alias_name%&lt;/code&gt; part of &lt;code&gt;FROM&lt;/code&gt; statement)&lt;/li&gt;
&lt;li&gt;replace used aliases with image indices (&lt;code&gt;--from=deps&lt;/code&gt; becomes &lt;code&gt;--from=0&lt;/code&gt; and &lt;code&gt;--from=BUILD_IMAGE&lt;/code&gt; becomes &lt;code&gt;--from=1&lt;/code&gt;)
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Our final Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# dependencies image
FROM node:14-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

# build image
FROM node:14-alpine
WORKDIR /app
COPY --from=0 /app/node_modules ./node_modules
COPY . .
RUN yarn build
RUN rm -rf node_modules
RUN yarn install --production --frozen-lockfile --ignore-scripts --prefer-offline

# build output image
FROM node:14-alpine

ENV NODE_ENV production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
WORKDIR /app
COPY --from=1 --chown=nextjs:nodejs /app/package.json /app/yarn.lock ./
COPY --from=1 --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=1 --chown=nextjs:nodejs /app/public ./public
COPY --from=1 --chown=nextjs:nodejs /app/.next ./.next

USER nextjs

EXPOSE 3000

CMD [ "yarn", "start" ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also create &lt;code&gt;.dockerignore&lt;/code&gt; file near &lt;code&gt;Dockerfile&lt;/code&gt; and insert two directory names into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make sure it works run the following commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker build -t example-nextjs-app .&lt;/code&gt; - builds our image&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker run -p 3000:3000 example-nextjs-app&lt;/code&gt; - runs our image
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now open &lt;code&gt;localhost:3000&lt;/code&gt; in your browser - you should see your app up and running. Yay =)&lt;br&gt;&lt;br&gt;
To stop docker image either kill (close) the terminal or stop the image:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;execute command &lt;code&gt;docker ps&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;copy container id&lt;/li&gt;
&lt;li&gt;execute command &lt;code&gt;docker stop container id&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/bnn1/deploying-dockerized-nextjs-app-to-aws-eb-part-2-deployment-to-beanstalk-47pj"&gt;next part&lt;/a&gt; we are going to deploy our application to AWS Elastic Beanstalk. Hope to see you in the  =)&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
