<?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: Reece Charsville</title>
    <description>The latest articles on Forem by Reece Charsville (@charsville).</description>
    <link>https://forem.com/charsville</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%2F666909%2F72c5c95b-7d87-48de-8543-a3e499c2cefc.jpeg</url>
      <title>Forem: Reece Charsville</title>
      <link>https://forem.com/charsville</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/charsville"/>
    <language>en</language>
    <item>
      <title>How to optimise a Next.js site for maximum performance</title>
      <dc:creator>Reece Charsville</dc:creator>
      <pubDate>Tue, 31 Jan 2023 12:24:32 +0000</pubDate>
      <link>https://forem.com/charsville/how-to-optimise-a-nextjs-site-in-2023-3ch3</link>
      <guid>https://forem.com/charsville/how-to-optimise-a-nextjs-site-in-2023-3ch3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post was originally posted on &lt;a href="https://forward.digital/blog/how-to-optimise-a-nextjs-site" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Recently at Forward Digital, we have had a big push on SEO. A key part of this is the performance of your website. There are a lot of resources out there supplied by Google, that go into detail on how performant your website should be, and what metrics are used to track performance.&lt;/p&gt;

&lt;p&gt;So, we have spent a lot of time optimising our site for maximum performance, and we thought we would share some useful tips and how we did it.&lt;/p&gt;

&lt;p&gt;To give some context, our website is built using Next.js and hosted on Vercel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools for tracking performance
&lt;/h2&gt;

&lt;p&gt;The process of optimising your website is incremental. It is important to track how each change is affecting performance. By doing this you can gain a better understanding of where to prioritise your efforts. Thankfully, Chrome Dev Tools is a great tool for doing this, it has Lighthouse built into it which enables you to quickly run reports and calculate a performance score.&lt;/p&gt;

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

&lt;p&gt;This is what we used to track our progress optimising our site, and what I will keep referring to in this post.&lt;/p&gt;

&lt;p&gt;Now some important things to note. The first is that you should track your performance based on your mobile performance.&lt;/p&gt;

&lt;p&gt;Generally speaking, desktop performance is much better than mobile, and so you will notice that when analysing sites via Lighthouse the performance score on mobile is much lower than on desktop. Therefore if we can get good mobile performance then we know we should have a really good desktop performance.&lt;/p&gt;

&lt;p&gt;The next important thing to note is that your lighthouse score can change each time you run a report, even though you may have made no changes. If you run your website locally, open localhost and run the lighthouse report you will notice the score is significantly lower than when you run lighthouse on your live site. Then if you try opening an incognito window and running the reports again, the score may have changed again.&lt;/p&gt;

&lt;p&gt;This is because of two things. Firstly when you are running your code locally with &lt;code&gt;next dev&lt;/code&gt;, you are not running it in production mode. This means that there are lots of extra debugging and dev tools included in the bundle and files are not being minified. This causes slower performance.&lt;/p&gt;

&lt;p&gt;Secondly, when you are running your browser outside of an incognito window you will have all of your browser extensions. Some browser extensions will require extra resources and inject things onto the page. A good example of this is React Dev Tools. This then gives you are worse score than other users may experience who do not have any extensions installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Process for tracking performance
&lt;/h2&gt;

&lt;p&gt;In our case, to try and make sure we had a good understanding of our current performance we always ran our lighthouse checks in an incognito window. We also ran two reports after each change. We ran one on localhost, and then we would push our changes to a branch which had a Vercel preview deployment attached to it and run a report on the preview URL in an incognito window. This helps give a clear understanding of how our code changes have affected performance, and what we can expect our performance to be on a live deployment.&lt;/p&gt;

&lt;p&gt;Before you start making changes to your code, run these two reports and make note of the mobile performance scores. Each time we make a change we can run the reports again and compare the results.&lt;/p&gt;

&lt;p&gt;It can become a tedious task making changes, pushing to preview and then waiting for it to deploy, just to run the performance report again. So when you run the report on your localhost, if your performance score has not increased by at least 2, then I would not bother pushing it, as the change is unlikely to have made much difference.&lt;/p&gt;

&lt;p&gt;However, it is important to note that even though your score on localhost may only have increased by 2, it does not mean that the preview deployment will have only increased by 2. In our case, we would sometimes see a change of 2 on localhost, which was more like 8/10 when on the preview URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does Google measure performance
&lt;/h2&gt;

&lt;p&gt;There are 6 key metrics that are measured, and these are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First Contentful Paint (FCP)&lt;/li&gt;
&lt;li&gt;Time to Interactive (TTI)&lt;/li&gt;
&lt;li&gt;Speed Index&lt;/li&gt;
&lt;li&gt;Total Blocking Time&lt;/li&gt;
&lt;li&gt;Largest Contentful Paint (LCP)&lt;/li&gt;
&lt;li&gt;Cumulative Layout Shift (CLS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I won’t go into too much detail on what each of these mean, but Google actually supply us with numbers that they see as Good, Needs Improvement or Poor. We can use these as our targets.&lt;/p&gt;

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

&lt;p&gt;You can read more about what each of these metrics means and how they are measured &lt;a href="https://support.google.com/webmasters/answer/9205520#status_bucket" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I have attached some more resources at the end of this post if you want to dive in further on each of them.&lt;/p&gt;

&lt;p&gt;You will notice in these metrics they show FID, which is not on the Lighthouse metrics. FID stands for First Input Delay, and it is very similar to Total Blocking Time. So, we should aim to get our Total Blocking Time to less than 100ms.&lt;/p&gt;

&lt;p&gt;These targets are NOT for our localhost reports. These are the targets for the preview deployment report. It will be almost impossible to achieve these on our localhost report, and the preview deployment will be much closer to what the live deploy report will be. The ultimate goal is to achieve these targets with the live URL.&lt;/p&gt;

&lt;p&gt;Now let’s get into actually improving our scores!&lt;/p&gt;

&lt;h2&gt;
  
  
  Image Optimisation
&lt;/h2&gt;

&lt;p&gt;Next.js includes image optimisation via the &lt;code&gt;next/image&lt;/code&gt; component. If you deploy your site using &lt;code&gt;next export&lt;/code&gt; then you may want to skip this section, as you will probably be aware that the component cannot be used the same with exported sites.&lt;/p&gt;

&lt;p&gt;Try to make sure that you are using this component instead of &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags everywhere.&lt;/p&gt;

&lt;p&gt;You can read the docs &lt;a href="https://nextjs.org/docs/api-reference/next/image" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It may cause some slight pains with sizing your images on the page but it is worth it for the benefits. You can also use the &lt;a href="https://nextjs.org/docs/api-reference/next/image#fill" rel="noopener noreferrer"&gt;fill prop&lt;/a&gt; with a relatively positioned container to help make this easier.&lt;/p&gt;

&lt;p&gt;When using the Image component there is a &lt;code&gt;priority&lt;/code&gt; prop. This can help improve your FCP and LCP scores. If you have any images “above the fold” when your site first loads, then these should all have this set to true. It ensures that these images are loaded first.&lt;/p&gt;

&lt;p&gt;You can also use the &lt;code&gt;quality&lt;/code&gt; prop to set a number between 1 and 100. It defaults to 75 but can be used to help make larger images load faster.&lt;/p&gt;

&lt;p&gt;Here is an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Image
    src="me.png"
    alt="Picture of the author"
    width={500}
    height={500}
    quality={60}
    priority
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Custom Fonts and Google Font Optimisation
&lt;/h2&gt;

&lt;p&gt;Whether you are loading your own font files or loading from a CDN like Google Fonts you can optimise this. Loading in fonts can increase your Total Blocking Time and your Time to Interactive.&lt;/p&gt;

&lt;p&gt;In our case, we use a custom font that is included in our website bundle.&lt;/p&gt;

&lt;p&gt;Next.js has a package called &lt;code&gt;@next/font&lt;/code&gt; that allows you to optimise your fonts. It is not included with Next so you must install this package separately. You can read the documentation &lt;a href="https://nextjs.org/docs/basic-features/font-optimization" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As we host our own font files we needed to use the &lt;a href="https://nextjs.org/docs/basic-features/font-optimization#local-fonts" rel="noopener noreferrer"&gt;Local Fonts&lt;/a&gt; method of optimising fonts. This required us to define the paths to our files in an object, and inject them into the main of our app.&lt;/p&gt;

&lt;p&gt;The process is similar for Google Fonts, however, you can import these directly from the package.&lt;/p&gt;

&lt;p&gt;As the fonts are injected via className it means that you can only include fonts for specific pages or components where the font is used.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;Roboto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@next/font/google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;roboto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Roboto&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;subsets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latin&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;roboto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&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;h2&gt;
  
  
  Lazy Loading Content
&lt;/h2&gt;

&lt;p&gt;When a page is first loaded we want to prioritise the content above the fold. The LCP is referring to the largest element that the user will see first. In order to get this time down we want it to be one of the first things to load. This is where &lt;code&gt;next/dynamic&lt;/code&gt; can help. You can read the documentation &lt;a href="https://nextjs.org/docs/advanced-features/dynamic-import" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In our case, our pages are broken down into smaller React components. Each section of our homepage is its own component and all of the content above the Fold is wrapped in a Fold component. This approach makes using the dynamic import packages really easy.&lt;/p&gt;

&lt;p&gt;We wrapped all of the components below the fold in dynamic imports. This means that all of the content in the FCP and LCP is prioritised, reducing those all-important times.&lt;/p&gt;

&lt;p&gt;Here is a quick code snippet to show you how it looks:&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;dynamic&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/dynamic&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;Fold&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Components&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;WhatWeDo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/what-we-do&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;AboutUs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/about-us&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;Services&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/services&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&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="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Fold&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;WhatWeDo&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;AboutUs&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;Services&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3rd Party Scripts
&lt;/h2&gt;

&lt;p&gt;You may use 3rd party scripts for certain libraries or things like Google Analytics. These need to be loaded into the page, and if they are required then they can increase your TTI and Total Blocking Time.&lt;/p&gt;

&lt;p&gt;You can use the &lt;code&gt;next/script&lt;/code&gt; components to optimise these imports (Documentation is &lt;a href="https://nextjs.org/docs/basic-features/script" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Some scripts do not need to be loaded in before the page renders and so you can utilise the strategy prop on the Script component. By default the strategy is &lt;code&gt;afterInteractive&lt;/code&gt; this means that these are not loaded until the TTI has finished.&lt;/p&gt;

&lt;p&gt;You can also use &lt;code&gt;lazyOnLoad&lt;/code&gt;, this is useful for any scripts that are for content below the fold or that can be loaded while the user is already using the site.&lt;/p&gt;

&lt;p&gt;If there are certain scripts that must be loaded before you can use &lt;code&gt;beforeInteractive&lt;/code&gt; but you should try to do this as little as possible as it will have a negative impact on the performance.&lt;/p&gt;

&lt;p&gt;Google Analytics can actually be rendered later. However, one thing to note is that your analytics will not track users who did not complete loading the page.&lt;/p&gt;

&lt;p&gt;Here is an example of the script component in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Script src="https://example.com/script.js" strategy="lazyOnload" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Don’t use Babel compiler, use Next.js compiler
&lt;/h2&gt;

&lt;p&gt;In version 12 of Next.js they introduced the new compiler, that would replace the Babel compiler. The new compiler is much more performant and effective at optimising your final bundle.&lt;/p&gt;

&lt;p&gt;When we first built our website we were using version 11 of Next which came with the babel compiler. Even after upgrading to the latest version we still had a custom &lt;code&gt;.babelrc&lt;/code&gt; file in our project. Just by having this file it meant that when we compiled our website it would use the babel compiler instead of the Next.js one. So we removed the file entirely and re-compiled and saw a huge increase in performance.&lt;/p&gt;

&lt;p&gt;As of Next 13 the Next.js swc compiler removed the use of Terser for minification, and the swc compiler handles all minification which is 7x faster. So make sure you are running the latest version of Next without any babel config.&lt;/p&gt;

&lt;p&gt;You can read up more about the compiler &lt;a href="https://nextjs.org/docs/advanced-features/compiler" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another thing to check is that in your next.config.js you do not have the swcMinifier disabled.&lt;/p&gt;

&lt;p&gt;For example, this is bad as it will use the slower Terser minifier:&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;// next.config.js&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;swcMinify&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bundle size
&lt;/h2&gt;

&lt;p&gt;Here is the big one! Most of the previous tips try to reduce the number of render-blocking resources and optimise what gets loaded and when. However, ultimately one of the most important things you need to maximise performance is a smaller bundle size.&lt;/p&gt;

&lt;p&gt;Most of us know what it’s like to be waiting for downloads, and the dreaded feeling when you start a download and see almost 100GB+. Websites are not so different. The smaller the bundle, the faster it downloads. The main difference when it comes to websites is that we are working with much smaller numbers.&lt;/p&gt;

&lt;p&gt;If you were using the Babel compiler and moved to the Next.js compiler then you will have probably already managed to get your bundle down somewhat. I will run you through some more ways of getting the bundle size down.&lt;/p&gt;

&lt;h3&gt;
  
  
  next bundle analyzer
&lt;/h3&gt;

&lt;p&gt;The first step to reducing the bundle size is analysing what you currently have. This way you can see what are the biggest contributors to your bundle, and look for ways to reduce them. In some cases, you may even want to remove them.&lt;/p&gt;

&lt;p&gt;Follow the instructions to install &lt;code&gt;@next/bundle-analyzer&lt;/code&gt; &lt;a href="https://www.npmjs.com/package/@next/bundle-analyzer" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Then run a build. You will see a few windows open in your browser.&lt;/p&gt;

&lt;p&gt;The first one to look at is the &lt;code&gt;client.html&lt;/code&gt;. This shows you a visual representation of all the files included in your bundle. The size of the file or directory on the screen is proportionate to the size of it in the bundle.&lt;/p&gt;

&lt;p&gt;Take a look at this example:&lt;/p&gt;

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

&lt;p&gt;There are a few that stand out to me. These are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;react-fullpage.js&lt;/li&gt;
&lt;li&gt;react-dom.production.min.js&lt;/li&gt;
&lt;li&gt;data-protection-policy.mdx&lt;/li&gt;
&lt;li&gt;prism.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will notice that 3 of these are wrapped in node_modules. This means that they are some of the libraries that we have installed in our project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;react-dom&lt;/code&gt; there is not much we can do about that, as we need React installed.&lt;/p&gt;

&lt;p&gt;However, &lt;code&gt;react-fullpage.js&lt;/code&gt; is a 3rd party library we installed for some fancy slide animations. When I hover it I can see that it has a parsed size of 73.36KB. This is significantly larger than most of the other files and directories in the bundle. Therefore, this would be a great place to start.&lt;/p&gt;

&lt;p&gt;The first step is to see if I can remove it entirely. If I remove it then I will be saving 73.36KB which would reduce my total bundle size from 836.78 KB to 763.42 KB, which equates to around 11.4% reduction. This is a significant reduction in size.&lt;/p&gt;

&lt;p&gt;How do I know if I can remove it? This is where depcheck comes into play.&lt;/p&gt;

&lt;h3&gt;
  
  
  depcheck
&lt;/h3&gt;

&lt;p&gt;depcheck is a package that analyses your code and helps determine if you have any packages installed that are not being used. You can read the docs &lt;a href="https://www.npmjs.com/package/depcheck" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You do not  need to install the package, you can run this command in the root of your project&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="nx"&gt;npx&lt;/span&gt; &lt;span class="nx"&gt;depcheck&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will output to the console a list of your dependencies. There is a section with the heading *******************************&lt;strong&gt;&lt;em&gt;Unused dependencies&lt;/em&gt;&lt;/strong&gt;*******************************. This is where you should focus your attention.&lt;/p&gt;

&lt;p&gt;According to depcheck the packages under this section are not being used in your app, so ******&lt;strong&gt;&lt;em&gt;in theory,&lt;/em&gt;&lt;/strong&gt;****** you can remove all of these packages.&lt;/p&gt;

&lt;p&gt;However, be careful. Just because depcheck can’t find any reference to them it doesn’t mean they are not being used. I would recommend uninstalling them one by one and re-running your app to check it still builds and works as expected.&lt;/p&gt;

&lt;p&gt;Removing any unused packages can help reduce bundle size without having any effect on the app, but in our example, we are using &lt;code&gt;react-full-page&lt;/code&gt; which means we can’t just remove the dependency. So the next step is to see if we can reduce it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Light versions
&lt;/h3&gt;

&lt;p&gt;If you have a package that is taking up most of your bundle size then it may be a good idea to look for an alternative. There are often a lot of packages out there that do similar jobs but have different bundle sizes. A good example of this is Lottie animations.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lottie-web&lt;/code&gt; is the official package that is used, however, this has a massive bundle size. We were using &lt;code&gt;react-lottie-player&lt;/code&gt; which had a dependency on lottie-web and it took up almost 40% of our total bundle size when we ran the analyser.&lt;/p&gt;

&lt;p&gt;After looking through the docs, we found that there was a light version of Lottie that react-lottie-player provided in the distribution. So we updated all of our references to Lottie to use the light version. This reduced the bundle size a lot and got it down from 40% to around 20%.&lt;/p&gt;

&lt;p&gt;This was a big performance boost, and after re-running our performance reports we saw a big difference. But even at 20% it was still one of our biggest contributors to the bundle size. So we pushed on to see if there were any other ways we could reduce it.&lt;/p&gt;

&lt;p&gt;Introducing dotlottie. Dotlottie is a new format and allows you to convert Lottie .json into the new and smaller .lottie format. We can then install the &lt;code&gt;@dotlottie/player-component&lt;/code&gt; package instead of the &lt;code&gt;react-lottie-player&lt;/code&gt; one. This removed the dependency on &lt;code&gt;lottie-web&lt;/code&gt; entirely and got the bundle size of the Lottie animations so small I can’t even see it on the analyser!&lt;/p&gt;

&lt;p&gt;Not all packages have light versions, so you may need to try something else. What next?&lt;/p&gt;

&lt;h3&gt;
  
  
  Tree shaking
&lt;/h3&gt;

&lt;p&gt;Tree shaking is the term used to describe when dead code is removed from a bundle. Lots of packages will implement tree shaking, and in this context, it means that when your app is compiled it will only include the specific files and assets that you are using in that package.&lt;/p&gt;

&lt;p&gt;Some packages will not have a good implementation of tree shaking. An example of this (at the time of writing this post) is &lt;code&gt;react-icons&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We used react icons across our website and used various icons from the material design icon set. Across the entire site, we probably used about 10 different icons. Overall it is a very small subset of a large icon set.&lt;/p&gt;

&lt;p&gt;When we ran the bundle analyser we could see that react-icons was including the entire material design icon set in the final bundle. This is an example of failed tree shaking. We should only be including the 10 icons we used.&lt;/p&gt;

&lt;p&gt;After looking around online it seemed that this was a known issue with react-icons and Next.js. The solution was to install a new package that provided all of the individual icon files (&lt;code&gt;@react-icons/all-files&lt;/code&gt;). Using this package we could import all of our icons by specifying the exact paths to the 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;MdMail&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-icons/all-files/md/MdMail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This then meant that only the 10 icons we used were included in the final bundle.&lt;/p&gt;

&lt;p&gt;When you are looking at some of the larger packages in your bundle, you should try to see if it uses an effective tree shaking approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Last resort
&lt;/h3&gt;

&lt;p&gt;If you find that you are using a package that has no light version or alternative, then you may want to consider writing your own implementation.&lt;/p&gt;

&lt;p&gt;In our case &lt;code&gt;react-fullpage-js&lt;/code&gt; is being used for some nice CSS animations. It includes a lot of other functionality but we are only using a small part of it. So it wouldn’t be too much of a big job to write our own CSS animations to replace it and remove the dependency.&lt;/p&gt;

&lt;p&gt;However, this may be too much of an undertaking for some more complex third-party packages, and it also becomes more code for you to maintain yourself. So tread lightly with this one!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;After implementing all of the above techniques on our site, we were able to see massive performance gains. Check out our website, and you can see what I mean &lt;a href="https://forward.digital" rel="noopener noreferrer"&gt;forward.digital&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have gathered a list of some of the resources that I found useful when researching and optimising our website performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Useful links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/mainthread-work-breakdown/" rel="noopener noreferrer"&gt;Minimizing main thread work issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/devtools/lighthouse/#main" rel="noopener noreferrer"&gt;How to use the dev tools performance tab to determine main thread work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/apply-instant-loading-with-prpl/" rel="noopener noreferrer"&gt;PRPL design patterns for optimisation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/first-contentful-paint/" rel="noopener noreferrer"&gt;More info on First Contentful Paint (FCP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/speed-index/" rel="noopener noreferrer"&gt;More info on Speed Index&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/interactive/" rel="noopener noreferrer"&gt;More info on Time to Interactive (TTI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/" rel="noopener noreferrer"&gt;More info on Total Blocking Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/" rel="noopener noreferrer"&gt;More info on Largest Contentful Paint (LCP)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>softwareengineering</category>
      <category>designpatterns</category>
      <category>gameengine</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Web app vs mobile app: Which should I use?</title>
      <dc:creator>Reece Charsville</dc:creator>
      <pubDate>Tue, 17 Jan 2023 09:26:02 +0000</pubDate>
      <link>https://forem.com/charsville/web-app-vs-mobile-app-which-should-i-use-3n4</link>
      <guid>https://forem.com/charsville/web-app-vs-mobile-app-which-should-i-use-3n4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post originally appeared on my &lt;a href="https://forward.digital/blog/web-app-vs-mobile-app"&gt;blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When working with a client one of the most frequent questions we get asked is “should I use a web app or a mobile app?”. There isn't a one-size-fits-all answer. The answer depends on a few factors specific to your project and its requirements. I will outline some of the key differences between the two, the benefits of each and the cost implications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Push Notifications
&lt;/h2&gt;

&lt;p&gt;A push notification is a message that is sent to a user's mobile device by a mobile application that the user has installed. These notifications can be used to alert users to new content or updates within the app, such as a new message, breaking news, or a sale. Push notifications are often delivered in real-time and can be customized to include things like images, sounds, or vibrations to grab the user's attention. However, push notifications are not available on web applications.&lt;/p&gt;

&lt;p&gt;One of the main benefits of push notifications is that they can be used to re-engage users with an app. When users receive a push notification, they are more likely to open the app and interact with the content or feature that the notification is promoting. This can be particularly useful for businesses, as it can lead to increased sales or engagement. Additionally, push notifications can also help to keep users informed of important updates or news, without the need for them to open the app or check for updates manually. Furthermore, they increase retention and can help to improve brand loyalty.&lt;/p&gt;

&lt;p&gt;The catch to this is that push notifications must be used carefully. If a user is frequently receiving notifications that are not relevant then they may start to find them annoying and this could lead to the user uninstalling your app entirely.&lt;/p&gt;

&lt;p&gt;Push notifications are often not a requirement for your application but can be a great addition to help improve user experience. Although they are not available on web applications, there are alternative solutions that often suffice. For example email alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offline Capability
&lt;/h2&gt;

&lt;p&gt;A key benefit of a mobile app is its offline capabilities. When working with a web app you can only access it when you have an internet connection. This can be a particular problem if a user is trying to use your app when they are in an area with bad service or is using a device that relies on WiFi and does not have internet access.&lt;/p&gt;

&lt;p&gt;Mobile applications are installed onto the device. This means that they can still run and perform certain actions without connecting to the internet. If your app is able to still function without an internet connection then this can be a very powerful tool. It is also possible to queue requests. This means that even if your mobile app does require an internet connection, requests can be queued and sent off when the device regains internet access.&lt;/p&gt;

&lt;p&gt;However, there are some caveats to this. Mobile app development is not straightforward and so integrating a queue system into your mobile app is going to take an app developer some time. This can therefore have some implications on the total cost.&lt;/p&gt;

&lt;p&gt;Is it a big deal? More often than not whether its a web app or a mobile app they require an internet connection to store data, so it is not uncommon to see applications not working without it. Not only that, but internet access is becoming much more widespread anyway. Sometimes a simple indication to the user that they are offline is enough.&lt;/p&gt;

&lt;p&gt;Personally, I would say offline capability is only a real benefit if your app can perform its duties without internet access. The exception to this would be that you have the budget to implement some more advanced functionality to handle an offline mode. If you don't need an offline mode then this should not sway you to a mobile app instead of a web app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Device Capabilities
&lt;/h2&gt;

&lt;p&gt;Mobile apps are developed to run on specific platforms, such as iOS or Android, and have access to the device's hardware, software and capabilities like the camera, GPS, sensors, microphone, and more. This allows them to provide a more engaging experience to users. For example, a mobile camera app can directly access the device's camera and take photos, a navigation app can access the GPS to provide turn-by-turn directions, and a fitness app can access the device's accelerometer to track movement and activity.&lt;/p&gt;

&lt;p&gt;Web applications, on the other hand, are limited to the features and capabilities provided by the browser. For example, a web application that is designed to take photos would need to use the device's camera through the browser's built-in features, which would not provide the same level of control and functionality as a mobile app. Similarly, a web application can access the device's location through the browser by using the Geolocation API, but the level of accuracy, granularity and details available is not comparable with a mobile app.&lt;/p&gt;

&lt;p&gt;Access to device capabilities is important when you are trying to decide between a mobile app and a web application. For some apps, access to the mobile device functionality is integral to the functionality and therefore it makes sense for it to be a mobile app. For others, the device functionality can be used to improve the user experience or add to the overall seamlessness of the app, but it is not required or always worth the extra cost and app development time.&lt;/p&gt;

&lt;h2&gt;
  
  
  User Experience
&lt;/h2&gt;

&lt;p&gt;The user experience (UX) of a mobile app and a web app can be quite different, due to the way they are designed and the platforms on which they run.&lt;/p&gt;

&lt;p&gt;Mobile apps are specifically designed for smaller screens and touch-based interfaces. They are typically optimized for use on a single device, such as a smartphone or tablet. This allows for a more seamless and intuitive experience, as the design and layout of the app can be tailored to the specific device and its capabilities. Mobile applications also tend to be faster, smoother, and more responsive than web apps, as they are developed to take advantage of the device's hardware.&lt;/p&gt;

&lt;p&gt;Web apps, on the other hand, are designed to be accessed on a wide variety of devices and screen sizes. They are typically optimized for use with a mouse and keyboard, and the layout and design must be able to adapt to different screen sizes and resolutions. They can also bean less performant as they rely on the internet connection, and the experience may vary depending on the quality of the connection.&lt;/p&gt;

&lt;p&gt;In terms of software development, a mobile app can be much more difficult, especially if building a native app. It requires in-depth knowledge of the differences between Android and iOS and how to handle that. Hybrid applications can help solve some of these issues but there is still a lot that needs to be done as part of the mobile app testing phase to ensure you create the best UX for your cross platform app. Web app development is much easier as it relies on more widely used web technologies like HTML, CSS and Javascript. There are also a lot of resources and libraries out there to aid with UX design and development, making it much easier for a web developer. This can mean a better user experience can be developed in a shorter amount of time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Play Store/Apple App Store
&lt;/h2&gt;

&lt;p&gt;Mobile apps are typically distributed through app stores, such as the Apple App Store or Google Play Store. These app stores give developers a way to reach a large audience of potential users. When an app is made available on an app store, it can be easily discovered by users through search and browsing. Additionally, the app store's ranking and curation system can be leveraged to promote popular or well-reviewed apps, which can give them greater visibility.&lt;/p&gt;

&lt;p&gt;App stores can also provide other features that improve discoverability, such as recommendations, featured apps and charts that show the top apps based on downloads or user ratings. This can make it easier for users to discover and download new apps, even if they are not actively looking for them. Additionally, App stores also provide reviews, ratings and app descriptions to help users decide if the app is worth downloading or not.&lt;/p&gt;

&lt;p&gt;However the stores are very crowded places, so it is still a very competitive market when trying to appear higher up in the stores. It is also a lot more work that you need to consider, there are things like creating assets for store pages, writing store page listings, curating required policy documents for each app store and ensuring your app can make it through Apple's strict review system. These additional things take more time and therefore cost more money.&lt;/p&gt;

&lt;p&gt;Web applications need to be accessed via a web browser and require the user to know the URL of the application. Unlike mobile apps, web apps don't have a centralized distribution point, which means they aren't discoverable through a single platform or marketplace. Users have to find the web application by searching the web, or through promotions, advertising or word of mouth. This can make it more challenging for web applications to reach a large audience and acquire new users. This is where SEO comes into play. There is an entire industry built around getting websites and web apps higher up in search and making them more discoverable. Meaning there are a lot of options out there for aiding in discoverability, whether it's online tools or SEO professionals. In some cases, you may find that good SEO is more powerful than just being on the app stores, and you don't have to worry about all of the extra work involved in getting your mobile app on the app store.&lt;/p&gt;

&lt;p&gt;Another benefit of using a web app is that it is a much easier process to push out updates. Mobile apps require you to generate new builds for both your android app and your ios app, which must then be uploaded to the stores. You then need to create the new release on the stores and submit them for review before they can be pushed out to users. Once they pass review it is not going to be instantaneous that every user is on the latest version, as it is down to the user when they install the updates. On the other hand, when you want to update your web app it is just a case of pushing the update to your hosting service. Once the update is deployed anyone who accesses your web app will be on the latest version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;In the previous sections, we have touched on a few of the factors that can cause mobile applications to be a little more expensive than web applications. It is not always the case but I will reiterate and outline the differences.&lt;/p&gt;

&lt;p&gt;The first thing to consider is complexity. Does your app require the use of device hardware? If it is a necessity then you may find that it is more cost-effective to build a mobile application. This is because it is easier for a mobile application to access device hardware (e.g camera or GPS). But, generally speaking, mobile app development is harder than web app development, so if you do not require access to specific mobile device functionality then it will most likely be cheaper to develop a web app. This does not just apply to the initial development phase either, software needs to be maintained and due to mobile app development being more complex, it can mean maintenance costs are also higher.&lt;/p&gt;

&lt;p&gt;Hosting costs are another big thing to consider. When you build a mobile app you have the additional costs incurred by the Apple App Store and Google Play Store. This can mean more time is spent pushing updates, curating store assets and making updates to get your app through app store reviews. Whereas a web app is hosted with a provider of your choice. There are countless hosting providers out there so it is a competitive market. This makes it easier to get better deals on hosting. That's not to say web apps are always cheaper to host, as some web apps can become expensive if you are handling very high volumes of traffic, but it is easy to keep track of your traffic with tools like google analytics.&lt;/p&gt;

&lt;p&gt;If you do decide to go down the mobile platform route, then you must also decide whether you would like to have a native mobile app or a hybrid mobile app. In this case, a hybrid mobile app is significantly more cost-effective. A native application requires you to essentially build two apps, each with its own challenges and knowledge requirements.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>writing</category>
      <category>help</category>
      <category>startup</category>
    </item>
    <item>
      <title>Using an S3 object storage provider in Node.js</title>
      <dc:creator>Reece Charsville</dc:creator>
      <pubDate>Wed, 14 Jul 2021 09:21:51 +0000</pubDate>
      <link>https://forem.com/charsville/using-an-s3-object-storage-provider-in-node-js-6ck</link>
      <guid>https://forem.com/charsville/using-an-s3-object-storage-provider-in-node-js-6ck</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post originally appeared on my blog &lt;a href="https://forward.digital/blog/using-an-s3-object-storage-provider-in-nodejs"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Today I am going to be running through how to use an S3 object storage provider. &lt;/p&gt;

&lt;p&gt;(Just want to see the code? The GitHub is &lt;a href="https://github.com/Forward-Digital/node-s3-storage-example"&gt;here&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For those who don't know S3 object storage is a cloud service for hosting files. It is accessible via an API, which means it can easily be integrated into your projects. There are hundreds of uses cases but some of the most common involve hosting user-generated content and allowing users to upload profile images.&lt;/p&gt;

&lt;p&gt;Some of the most popular providers for S3 storage include Amazon AWS, Vultr and Digital Ocean. They all provide the same service but have a few differences when it comes to price, locations, capacities and bandwidths, so it's worth looking around to see which one suits your needs best.&lt;/p&gt;

&lt;p&gt;My first experience with S3 was using AWS. AWS is great.....but its also very confusing, especially for a backend developer like me who tries to stay clear of DevOps as much as he can. I trawled through the AWS documentation trying to understand how to implement the S3 service and after many hours of playing with buckets, policies and IAM roles I got it working. After my ordeal I decided to try other providers to see how the implementations differ (In the hope of finding a simpler solution). It turns out that implementations are the same across providers! &lt;/p&gt;

&lt;p&gt;So, I will run you through a very simple example of how to implement a basic S3 object storage in Nodejs. The example I am going to give uses Express and multer for the file upload, however the object storage code is framework agnostic and only requires the aws-sdk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparing our Node.js project
&lt;/h3&gt;

&lt;p&gt;Before we can connect to our S3 provider there are 4 things you will need. These are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The buckets endpoint URL&lt;/li&gt;
&lt;li&gt;The bucket name&lt;/li&gt;
&lt;li&gt;Access key&lt;/li&gt;
&lt;li&gt;Secret access key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These should be provided to you once you have set up your bucket through your chosen providers dashboard. You will want to ensure that your keys are kept private and securely. So in this example we will use dotenv environment variables. &lt;/p&gt;

&lt;p&gt;Firstly, lets create our &lt;code&gt;.env&lt;/code&gt; file in our project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;S3_BUCKET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;your_bucket_name&lt;/span&gt; &lt;span class="c1"&gt;// e.g my-bucket&lt;/span&gt;
&lt;span class="nx"&gt;S3_ENDPOINT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;your_endpoint_url&lt;/span&gt; &lt;span class="c1"&gt;// e.g https://eu.amazons3.com/&lt;/span&gt;
&lt;span class="nx"&gt;S3_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;your_access_key&lt;/span&gt;
&lt;span class="nx"&gt;S3_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;your_secret_access_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have the information for creating a connection, lets go ahead and install the packages for initialising a connection.&lt;/p&gt;

&lt;p&gt;The first thing we need is the &lt;code&gt;aws-sdk&lt;/code&gt; this is the npm package used for connecting and interacting with an S3 storage. Run the following command to install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;npm install aws-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example we are using TypeScript so we can also install some type definitions. If you are using JavaScript then you can ignore this step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;npm install --save-dev @aws-sdk/types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting up the connection
&lt;/h3&gt;

&lt;p&gt;Once installed we can create our &lt;code&gt;connection.ts&lt;/code&gt; :&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;S3&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk/clients/s3&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&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;S3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3_ENDPOINT_URL&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3_SECRET_KEY&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;Lets go through this code line by line. So firstly we import the S3 client from the aws-sdk. The aws-sdk includes a lot of features, so we only need to import the S3 client for this implementation. &lt;/p&gt;

&lt;p&gt;Next we create our Connect function. This function will new up an S3 client using the credentials that we stored in our environment variables. &lt;/p&gt;

&lt;p&gt;Our connect function takes in an optional &lt;code&gt;path&lt;/code&gt; parameter. When this is set we can specify the path that we want to upload our file to. For example we may want to upload an image to a subdirectory called images. So we would set the path to 'images'. This path is then appended to the endpoint URL. So as an example our endpoint now becomes &lt;code&gt;https://eu.amazons3.com/images&lt;/code&gt;. If we don't set the path parameter the connection will default to the buckets root.&lt;/p&gt;

&lt;p&gt;In our configuration we also provide an S3 API version. In this example I will use latest but you may want to pick a version that works for you. You can read up more about API versions and why you should pick one &lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/locking-api-versions.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uploading a file
&lt;/h3&gt;

&lt;p&gt;Now we have a working S3 client instance we can use it to upload files. Lets create a function for uploading a file. For this example we are using multer, so TypeScript users you can install the types with &lt;code&gt;npm i --save-dev @types/multer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;upload.ts&lt;/code&gt; will look like this:&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;PutObjectOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PutObjectRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk/clients/s3&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;AWSError&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk/lib/error&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;S3&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk/clients/s3&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;Connect&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./connection&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="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Multer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;objectName&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PutObjectRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;objectName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ACL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public-read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mimetype&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWSError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PutObjectOutput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;resolve&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3_ENDPOINT_URL&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;objectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our Upload function we are passing in 4 parameters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameters&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bucket&lt;/td&gt;
&lt;td&gt;This is the name of the bucket you set up with the provider and what we have stored in our environment variable (e.g my-bucket).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file&lt;/td&gt;
&lt;td&gt;This is the actual file that we are uploading.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;objectName&lt;/td&gt;
&lt;td&gt;This is the name that we would like to use when we store the file in the cloud. This name should include your file extension. If you are uploading a gif then this should be &lt;code&gt;image.gif&lt;/code&gt; as oppose to just &lt;code&gt;image&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;path&lt;/td&gt;
&lt;td&gt;(Optional) This is passed straight through to the connection we made previously. So by default it is set to null, which would mean the file is uploaded to the root of the bucket. If you supply &lt;code&gt;'images'&lt;/code&gt; to this parameter then the file you upload will be stored in a subdirectory called images.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our Upload function will return a Promise. This will resolve the URL of our uploaded file once the S3 client has finished uploading. &lt;/p&gt;

&lt;p&gt;Inside our new Promise, we first use our Connect function to get an initialised S3 client, passing through our optional &lt;code&gt;path&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;Then we create our S3 request parameters. In the parameters we set 5 options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameters&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bucket&lt;/td&gt;
&lt;td&gt;This is the name of the bucket. We set this using our bucket parameter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key&lt;/td&gt;
&lt;td&gt;This is the name that is used when the file is stored in the bucket. We use our objectName parameter here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Body&lt;/td&gt;
&lt;td&gt;This is the file we are uploading. This option takes a file buffer. So we use our parameter &lt;code&gt;file.buffer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACL&lt;/td&gt;
&lt;td&gt;This option is used to specify the access of the file we are uploading. In this example we are using &lt;code&gt;'public-read'&lt;/code&gt;. This means that anyone who has the URL of the file we upload can read it. If you want to read more about the different ACL types then read &lt;a href="https://amzn.to/2UoGMdh"&gt;here&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ContentType&lt;/td&gt;
&lt;td&gt;This is used to tell S3 the type of file we are uploading. It takes in a file mime type. We pass this in using our file parameters &lt;code&gt;file.mimetype&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Next we call the putObject method on the S3 client. We pass in our request parameters above, and define a callback. The callback will give us an error if the upload fails. So we can check if this has a value in our callback and reject our Promise if there is an error. If there is no error then we can resolve our promise with the URL of our object. We construct the URL of our uploaded object using the endpoint URL, bucket name, path and object name. So as an example if uploaded image.gif to an images folder inside our my-bucket, then the URL would be &lt;code&gt;https://eu.amazons3.com/my-bucket/images/image.gif&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deleting a file
&lt;/h3&gt;

&lt;p&gt;When it comes to deleting a file the process is very similar to upload. &lt;/p&gt;

&lt;p&gt;We can create a &lt;code&gt;delete.ts&lt;/code&gt;:&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;DeleteObjectOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DeleteObjectRequest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk/clients/s3&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;AWSError&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk/lib/error&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;S3&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk/clients/s3&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;Connect&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./connection&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="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;objectName&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DeleteObjectOutput&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="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DeleteObjectOutput&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;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DeleteObjectRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;objectName&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWSError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DeleteObjectOutput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;This function takes in 3 of the parameters we have seen before:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameters&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bucket&lt;/td&gt;
&lt;td&gt;The name of our bucket we created with the provider and stored in the environment variables.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;objectName&lt;/td&gt;
&lt;td&gt;The name that we used when storing the object. E.g &lt;code&gt;image.gif&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;path&lt;/td&gt;
&lt;td&gt;The path to the object. E.g &lt;code&gt;'images'&lt;/code&gt; would delete the object with the objectName supplied inside the images subdirectory. If null this defaults to the root of the bucket.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Inside our Promise we use our Connect function to get an initialised S3 client.&lt;/p&gt;

&lt;p&gt;We create our request parameters. Setting the &lt;code&gt;Bucket&lt;/code&gt; and &lt;code&gt;Key&lt;/code&gt; options using our functions parameters.&lt;/p&gt;

&lt;p&gt;Then we use the &lt;code&gt;deleteObject&lt;/code&gt; method on the client, passing in our request parameters and defining a callback. Just like before we check if the callback has errored and reject the promise if an error occurs.&lt;/p&gt;

&lt;p&gt;If no error occurs then we resolve the &lt;code&gt;deleteObject&lt;/code&gt; response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up our Express endpoints
&lt;/h3&gt;

&lt;p&gt;We have defined some functions to connect to our S3 provider, upload objects and delete objects. The next question is how do we use them?&lt;/p&gt;

&lt;p&gt;We will use Express and Multer as an example to demonstrate how to use them.&lt;/p&gt;

&lt;p&gt;Using our Express app we can define a POST endpoint like the following:&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;formFile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bad Request: No file was uploaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// If you want to retain the original filename and extension just use originalname like below&lt;/span&gt;
        &lt;span class="c1"&gt;// const filename: string = req.file.originalname;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;fileExtension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`my-custom-filename.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fileExtension&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3_BUCKET_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images/logo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an endpoint called /upload which accepts multi-part form data. We use the multer middleware with this endpoint. The multer middleware will look in the submitted form data for the field with the key &lt;code&gt;formFile&lt;/code&gt;. This key should be paired with a file. The middleware then attaches the file object to the request under the property &lt;code&gt;file&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;In our handler we check that a file has been supplied and throw a Bad Request response if none was sent.&lt;/p&gt;

&lt;p&gt;In the example I have shown how to use a custom filename. We read the file extension from our files original name first. Then we create a new filename, appending the original file extension e.g &lt;code&gt;my-custom-filename.gif&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;Next we call our Upload function. We pass in the bucket name stored in our environment variables; the file in the request; our custom filename; and in the example I am uploading to the subdirectory &lt;code&gt;images/logo&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;After awaiting our Upload we will have the URL of the uploaded file and we can send this in our endpoints response object.&lt;/p&gt;

&lt;p&gt;If you would like to see how to use the delete function with an Express endpoint then take a look at the example project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example project
&lt;/h3&gt;

&lt;p&gt;I have created a full working example project on GitHub which uses the code we have gone through today. Check it out &lt;a href="https://github.com/Forward-Digital/node-s3-storage-example"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>express</category>
    </item>
  </channel>
</rss>
