<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Inian</title>
    <description>The latest articles on DEV Community by Inian (@inian).</description>
    <link>https://dev.to/inian</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%2F495772%2Fa2f97517-3eeb-4fc7-b6c6-62be8557269d.png</url>
      <title>DEV Community: Inian</title>
      <link>https://dev.to/inian</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/inian"/>
    <language>en</language>
    <item>
      <title>Making the Supabase Dashboard Supa-fast</title>
      <dc:creator>Inian</dc:creator>
      <pubDate>Mon, 28 Dec 2020 13:58:28 +0000</pubDate>
      <link>https://dev.to/supabase/making-the-supabase-dashboard-supa-fast-mha</link>
      <guid>https://dev.to/supabase/making-the-supabase-dashboard-supa-fast-mha</guid>
      <description>&lt;p&gt;The Supabase dashboard has become more feature-rich in the last month. We have a powerful SQL editor backed by &lt;a href="https://microsoft.github.io/monaco-editor/" rel="noopener noreferrer"&gt;Monaco&lt;/a&gt;. We built an Airtable-like view of your database, making editing a breeze.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Features, performance, DX - choose three&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Performance can quickly regress when adding new features, especially in a Single Page Application. Here are the steps we took to guarantee a good baseline performance within our application, without compromising on the developer experience (DX).&lt;/p&gt;

&lt;h2&gt;
  
  
  Establishing a baseline and setting targets
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;You can't fix what you can't measure&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There was some low-hanging fruit to improve performance, but we had one important thing to do before that - establish a baseline.&lt;/p&gt;

&lt;p&gt;Our dashboard is JavaScript heavy, so we started by setting up analytics to track our bundle sizes. &lt;a href="https://www.npmjs.com/package/@next/bundle-analyzer" rel="noopener noreferrer"&gt;Next-bundle-analyzer&lt;/a&gt; (or &lt;a href="https://www.npmjs.com/package/webpack-bundle-analyzer" rel="noopener noreferrer"&gt;webpack-bundle-analyzer&lt;/a&gt;) provides an interactive treemap of your generated JavaScript bundles. This is our treemap when we started. It gave us a clear indication what changes we needed to achieve the most impact.&lt;/p&gt;

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

&lt;p&gt;There are some great tools when it comes to Real User Monitoring (RUM). We chose the newly-launched &lt;a href="https://sentry.io/for/performance/" rel="noopener noreferrer"&gt;Sentry performance monitoring&lt;/a&gt; product since we already use Sentry for error tracking and we wanted to minimize new tools in our stack. It also supports reporting &lt;a href="https://web.dev/vitals/" rel="noopener noreferrer"&gt;Core Web Vitals&lt;/a&gt;, the performance metrics created by Google to track initial loading performance, responsiveness and visual stability. Core Web Vitals come with recommended target values, giving us clear goals to hit.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Improving our JavaScript bundle size
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;How to not load the entire npm registry into our user's browsers&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Choosing smaller modules
&lt;/h3&gt;

&lt;p&gt;We used &lt;a href="https://bundlephobia.com/" rel="noopener noreferrer"&gt;Bundlephobia&lt;/a&gt; on our largest modules. This is a great website to have in your JS-performace arsenal. It gives the size of npm modules across different versions and recommends alternate modules with similar functionality which are smaller.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Moment.js&lt;/code&gt; is notorious for its large bundle size and we don't need complex date processing for our dashboard. It was straightforward to switch to &lt;a href="https://day.js.org/" rel="noopener noreferrer"&gt;day-js&lt;/a&gt; which is largely API-compatible with &lt;code&gt;Moment.js&lt;/code&gt;. This change reduced our gzipped bundle size by 68 KB.&lt;/p&gt;

&lt;p&gt;We migrated from &lt;code&gt;Joi&lt;/code&gt; to &lt;code&gt;ajv&lt;/code&gt; for our schema validation which was 32% smaller. &lt;code&gt;ajv&lt;/code&gt; was already bundled as a transitive dependency of other modules, making it a no-brainer.&lt;/p&gt;

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

&lt;p&gt;We reverted our &lt;a href="https://github.com/brix/crypto-js" rel="noopener noreferrer"&gt;crypto-js&lt;/a&gt; module from version 4.0 to 3.3.0. Version 4.0 &lt;a href="https://github.com/brix/crypto-js/issues/276" rel="noopener noreferrer"&gt;injects more than 400kb code&lt;/a&gt; when used in a browser context. The newer version replaces &lt;code&gt;Math.random&lt;/code&gt; with node's implementation, injecting the entire node crypto module into the browser context. We use &lt;code&gt;crypto-js&lt;/code&gt; for decrypting user's API keys and so we're not reliant on the randomness of the PRNG. We might move to a dedicated module like &lt;a href="https://www.npmjs.com/package/aes-js" rel="noopener noreferrer"&gt;aes-js&lt;/a&gt; in the future since it has a much smaller surface area than &lt;code&gt;crypto-js&lt;/code&gt; (in terms of security and performance).&lt;/p&gt;

&lt;h3&gt;
  
  
  Using partial imports
&lt;/h3&gt;

&lt;p&gt;By selectively importing functions from modules like &lt;code&gt;lodash&lt;/code&gt;, we cut the gzipped size by another 40kb across all our bundles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&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;lodash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// maunally cherry picking modules&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;find&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;lodash/find&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;debounce&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;lodash/debounce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// using babel-plugin-lodash&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;find&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;debounce&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;lodash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, we added &lt;a href="https://github.com/lodash/babel-plugin-lodash" rel="noopener noreferrer"&gt;babel-plugin-lodash&lt;/a&gt; to our babel configuration which cherry picks the exact &lt;code&gt;lodash&lt;/code&gt; functions we import. This makes it easier to import from &lt;code&gt;lodash&lt;/code&gt; without cluttering the code with selective import statements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving complex logic to the server
&lt;/h3&gt;

&lt;p&gt;Thanks to some skilled haxors (well, weak passwords mainly) we had crypto miners running on some of our customer's databases. To prevent this, we enforce password strength with the &lt;a href="https://github.com/dropbox/zxcvbn" rel="noopener noreferrer"&gt;zxcvbn&lt;/a&gt; module. Though it improved our overall security, the module is &lt;a href="https://bundlephobia.com/result?p=zxcvbn@4.4.2" rel="noopener noreferrer"&gt;pretty big&lt;/a&gt;, weighing in at 388kb gzipped. To get around this, we moved the password-strength checking logic to an API. Now, the frontend queries a server with a user-supplied password and the server computes its strength. This eliminates the module from the frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lazy loading code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/SheetJS/sheetjs" rel="noopener noreferrer"&gt;xlsx&lt;/a&gt; is another complex and large module, which is used to import spreadsheets into tables. We contemplated moving this logic to the backend, but we found another solution: lazy loading it.&lt;/p&gt;

&lt;p&gt;The spreadsheet import is triggered when the user is creating a new table. However the code was previously loaded every time the page was visited - even when a new table was not being created. This made it a good candidate for lazy loading. Using &lt;a href="https://nextjs.org/docs/advanced-features/dynamic-import" rel="noopener noreferrer"&gt;Next.js dynamic imports&lt;/a&gt; we are able to load this component (313 kb brotlied) dynamically, whenever the user clicks the "Add content" button.&lt;/p&gt;

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

&lt;p&gt;We use the same technique to lazy load some Lottie animations which are relatively large.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using native browser APIs
&lt;/h3&gt;

&lt;p&gt;We decided against supporting IE11, opening up more avenues for optimization. Using native browser APIs enabled us to drop even more dependencies. For example, since the &lt;a href="https://caniuse.com/fetch" rel="noopener noreferrer"&gt;fetch API is available&lt;/a&gt; in all the browsers we care about, we removed &lt;a href="https://github.com/axios/axios" rel="noopener noreferrer"&gt;axios&lt;/a&gt; and built a simple wrapper using the native fetch API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving Vercel's default caching
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The fastest request is the request not made&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We noticed that Vercel was sending a &lt;code&gt;Cache-Control&lt;/code&gt; header of &lt;code&gt;public, max-age=0, must-revalidate&lt;/code&gt; , preventing some of our SVG, CSS and font files from being cached in the browser.&lt;/p&gt;

&lt;p&gt;We updated our &lt;code&gt;next.config.js&lt;/code&gt; , adding a long &lt;code&gt;max-age&lt;/code&gt; to the caching header that Vercel sends. Our assets are versioned properly, so we were able to safely do this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Next.js Automatic Static Optimization
&lt;/h2&gt;

&lt;p&gt;Next.js is able to automatically pre-render a page to HTML, whenever a page meets some pre-conditions. This mode is called &lt;a href="https://nextjs.org/docs/advanced-features/automatic-static-optimization" rel="noopener noreferrer"&gt;Automatic Static Optimization&lt;/a&gt;. Pre-rendered pages can be cached on a CDN for extremely fast page loads. We removed calls to &lt;code&gt;getServerSideProps&lt;/code&gt; and &lt;code&gt;getInitialProps&lt;/code&gt; to take advantage of this mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developing a performance culture
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Always in sight, always in mind&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our performance optimization journey will never be complete. It requires constant vigilance to maintain a baseline across our users. To instill this within our team, we took a few actions.&lt;/p&gt;

&lt;p&gt;We developed a Slack bot which sends our Sentry performance dashboard every week, containing our slowest transactions and our Core Web Vitals summary. This shows which pages need improvement and where our &lt;a href="https://docs.sentry.io/product/performance/metrics/#user-misery" rel="noopener noreferrer"&gt;users are the most miserable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;During our transition from Alpha to Beta, performance was one of the important features, along with stability and security. We considered performance implications while choosing libraries and tools. Having a "seat at the table" in these discussions ensures that performance is not considered as an after-thought.&lt;/p&gt;

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

&lt;p&gt;With these changes, we have a respectable Core Web Vitals score. This is a snapshot from Sentry with RUM data from the last week. We are within the recommended threshold for all the 3 Web Vitals.&lt;/p&gt;

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

&lt;p&gt;Our Next.js build output also shows that users download &amp;lt; 200 kb of JavaScript between any two page transitions. We're still improving too - even though we provide a lot of functionality in our dashbaord, we will continue to reduce our bundle sizes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that did not work
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;You win some, you lose some&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We tried a VSCode plugin called &lt;a href="https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost" rel="noopener noreferrer"&gt;Import cost&lt;/a&gt; which shows the size of JavaScript modules when you import it in your editor. However, the plugin did not work on our codebase since it doesn't support some JavaScript features, like optional chaining.&lt;/p&gt;

&lt;p&gt;We also passed on using &lt;a href="https://www.npmjs.com/package/lodash-webpack-plugin" rel="noopener noreferrer"&gt;lodash-webpack-plugin&lt;/a&gt; even though it had the potential to reduce our JavaScript sizes, because it could potentially break our code if not used carefully. This plugin would require our frontend team to understand the Webpack configuration, updating it whenever they use a new lodash feature set.&lt;/p&gt;

&lt;h2&gt;
  
  
  The road ahead
&lt;/h2&gt;

&lt;p&gt;Our broad goal is to implement best practices for frontend performance, and make it exciting to all of our team. These are some ideas we have on our roadmap -&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up &lt;a href="https://developers.google.com/web/tools/lighthouse" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; in a Github Action to catch performance regression earlier in the development life cycle.&lt;/li&gt;
&lt;li&gt;Continue reducing our initial JavaScript payload size, to improve our LCP time&lt;/li&gt;
&lt;li&gt;Explore &lt;code&gt;cloud-mode&lt;/code&gt; in &lt;a href="https://segment.com/docs/connections/destinations/" rel="noopener noreferrer"&gt;Segment&lt;/a&gt; which makes API calls from the server instead of loading third-party library on the browser.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>performance</category>
      <category>supabase</category>
      <category>react</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
