<?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: @lukeocodes 🕹👨‍💻</title>
    <description>The latest articles on DEV Community by @lukeocodes 🕹👨‍💻 (@lukeocodes).</description>
    <link>https://dev.to/lukeocodes</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%2F133562%2F7ca76112-5580-4245-8a48-b24bf6f4fb51.jpg</url>
      <title>DEV Community: @lukeocodes 🕹👨‍💻</title>
      <link>https://dev.to/lukeocodes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lukeocodes"/>
    <language>en</language>
    <item>
      <title>Time to Leave? Time to Rebuild! Making Twitter2.0</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Mon, 12 Aug 2024 13:31:20 +0000</pubDate>
      <link>https://dev.to/lukeocodes/time-to-leave-time-to-rebuild-making-twitter20-4jgc</link>
      <guid>https://dev.to/lukeocodes/time-to-leave-time-to-rebuild-making-twitter20-4jgc</guid>
      <description>&lt;p&gt;The most critical features of a new social network for users fed up with Musk and Twitter, are as follows;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Import Twitter's archive.zip file&lt;/li&gt;
&lt;li&gt;Easy as possible to sign up&lt;/li&gt;
&lt;li&gt;Similar if not identical user features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Less critical but definitely helpful features of the platform;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ethically monetised and moderated&lt;/li&gt;
&lt;li&gt;Make use of AI to help identify problematic content&lt;/li&gt;
&lt;li&gt;Blue tick with the use of Onfido or SMART identity services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, we'll focus on the first feature. Importing Twitter's archive.zip file.&lt;/p&gt;

&lt;p&gt;If you'd rather not read my waffle and get straight to the juicy, here is the repo I published this script to;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/lukeocodes" rel="noopener noreferrer"&gt;
        lukeocodes
      &lt;/a&gt; / &lt;a href="https://github.com/lukeocodes/twitter-archive-extractor" rel="noopener noreferrer"&gt;
        twitter-archive-extractor
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Turn the data in your Twitter archive download into JSON.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Twitter Archive Extractor&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This Go program extracts and processes JavaScript files from a ZIP archive, specifically targeting files in the /data directory. It replaces certain window. assignments with var data = and outputs the JSON representation of the processed data.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How It Works&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;The program opens a ZIP file specified as a command-line argument.&lt;/li&gt;
&lt;li&gt;It scans for JavaScript files within the /data directory.&lt;/li&gt;
&lt;li&gt;For each JavaScript file, it replaces any window. assignments (e.g., window.__THAR_CONFIG = {) with var data =.&lt;/li&gt;
&lt;li&gt;It then uses the goja JavaScript interpreter to execute the modified script and extract the data variable.&lt;/li&gt;
&lt;li&gt;The extracted data is marshaled into JSON format and output to the console.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Todo&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Select a destination for export
&lt;ul&gt;
&lt;li&gt;separate files? via an ORM to a database?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Release to go.dev&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Ensure you have Go installed on your system.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Clone this repository or download the source files.&lt;/li&gt;
&lt;li&gt;Navigate to the project directory.&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/lukeocodes/twitter-archive-extractor" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The file
&lt;/h2&gt;

&lt;p&gt;Twitter haven't made your data all that easy to obtain. It's great that they give you access to it (legally, they have to). The format is crap. &lt;/p&gt;

&lt;p&gt;It actually comes as a mini web archive and all your data is stuck in JavaScript files. It is more of a web app than convenient storage of data.&lt;/p&gt;

&lt;p&gt;When you open up the &lt;code&gt;Your archive.html&lt;/code&gt; file you get something like this;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9r1ckcegy3w5oe36rpi7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9r1ckcegy3w5oe36rpi7.png" alt="My twitter archive.html file that I downloaded from Twitter is more like a web app than convenient storage of data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: I made the descision pretty early on to build using Next.js for the site, Go and GraphQL for the backend.&lt;/p&gt;

&lt;p&gt;So, what do you do when your data isn't structured data?&lt;/p&gt;

&lt;p&gt;Well, you parse it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a basic Go script
&lt;/h2&gt;

&lt;p&gt;Head on over to the &lt;a href="https://go.dev/doc/code" rel="noopener noreferrer"&gt;official docs on how to get started with Go&lt;/a&gt;, and set up your project directory.&lt;/p&gt;

&lt;p&gt;We're going to hack this process together. It seems one of the most important features to attract people who feel too attached to TwitterX.&lt;/p&gt;

&lt;p&gt;First step is to create a &lt;code&gt;main.go&lt;/code&gt; file. In this file we'll GO (hah) and do some STUFF;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;os.Args&lt;/code&gt;: This is a slice that holds command-line arguments.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;os.Args[0]&lt;/code&gt; is the program's name, and &lt;code&gt;os.Args[1]&lt;/code&gt; is the first argument passed to the program.&lt;/li&gt;
&lt;li&gt;Argument Check: The function checks if at least one argument is provided. If not, it prints a message asking for a path.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run&lt;/code&gt; function: This function simply prints the path passed to it, for now.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Path:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Please provide a path as an argument."&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="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;At every step, we'll run the file like so;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

go run main.go twitter.zip


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

&lt;/div&gt;

&lt;p&gt;If you don't have a Twitter archive export, create a simple &lt;code&gt;manifest.js&lt;/code&gt; file and give it the following JavaScript.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__THAR_CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userInfo&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accountId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1234567890&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lukeocodes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;displayName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Luke ✨&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Compress that into your &lt;code&gt;twitter.zip&lt;/code&gt; file that we'll use throughout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read a Zip file
&lt;/h2&gt;

&lt;p&gt;The next step is to read the contents of the zip file. We want to do this as efficiently as possible, and reduce time data is extracted on the disk.&lt;/p&gt;

&lt;p&gt;There are many files in the zip that don't need to be extracted, too.&lt;/p&gt;

&lt;p&gt;We'll edit the &lt;code&gt;main.go&lt;/code&gt; file;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Opening the ZIP file: The &lt;code&gt;zip.OpenReader()&lt;/code&gt; function is used to open the ZIP file specified by path.&lt;/li&gt;
&lt;li&gt;Iterating through the files: The function loops over each file in the ZIP archive using &lt;code&gt;r.File&lt;/code&gt;, which is a slice of &lt;code&gt;zip.File&lt;/code&gt;. The &lt;code&gt;Name&lt;/code&gt; property of each file is printed.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"archive/zip"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Open the zip file&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Iterate through the files in the zip archive&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Files in the zip archive:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Example usage&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Please provide the path to the zip file as an argument."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;
  
  
  JS only! We're hunting structured data
&lt;/h2&gt;

&lt;p&gt;This archive file is seriously unhelpful. We want to check for just &lt;code&gt;.js&lt;/code&gt; files, and only in the &lt;code&gt;/data&lt;/code&gt; directory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Opening the ZIP file: The ZIP file is opened using &lt;code&gt;zip.OpenReader()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Checking the &lt;code&gt;/data&lt;/code&gt; directory: The program iterates through the files in the ZIP archive. It uses &lt;code&gt;strings.HasPrefix(f.Name, "data/")&lt;/code&gt; to check if the file resides in the &lt;code&gt;/data&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Finding &lt;code&gt;.js&lt;/code&gt; files: The program also checks if the file has a &lt;code&gt;.js&lt;/code&gt; extension using &lt;code&gt;filepath.Ext(f.Name)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reading and printing contents: If a &lt;code&gt;.js&lt;/code&gt; file is found in the &lt;code&gt;/data&lt;/code&gt; directory, the program reads and prints its contents.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"archive/zip"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io/ioutil"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"path/filepath"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Open the file inside the zip&lt;/span&gt;
    &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Read the contents of the file&lt;/span&gt;
    &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ioutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// deprecated? :/ &lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Print the contents&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Contents of %s:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Open the zip file&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Iterate through the files in the zip archive&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JavaScript files in the zip archive:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Use filepath.Ext to check the file extension&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"data/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;".js"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c"&gt;// Exit after processing the first .js file so we don't end up printing a gazillion lines when testing&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Example usage&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Please provide the path to the zip file as an argument."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;
  
  
  Parse the JS! We want that data
&lt;/h2&gt;

&lt;p&gt;We've found the structured data. Now we need to parse it. The good news is there are existing packages for using JavaScript inside Go. We'll be using &lt;code&gt;goja&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're on this section, familiar with Goja, and you've seen the output of the file, you may see we're going to have errors in our future.&lt;/p&gt;

&lt;p&gt;Install &lt;code&gt;goja&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

go get github.com/dop251/goja


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

&lt;/div&gt;

&lt;p&gt;Now we're going to edit the &lt;code&gt;main.go&lt;/code&gt; file to do the following;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parsing with &lt;code&gt;goja&lt;/code&gt;: The &lt;code&gt;goja.New()&lt;/code&gt; function creates a new JavaScript runtime, and &lt;code&gt;vm.RunString(processedContents)&lt;/code&gt; runs the processed JavaScript code within that runtime.&lt;/li&gt;
&lt;li&gt;Handle errors in parsing&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"archive/zip"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io/ioutil"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"path/filepath"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Open the file inside the zip&lt;/span&gt;
    &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Read the contents of the file&lt;/span&gt;
    &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ioutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// deprecated? :/ &lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Parse the JavaScript file using goja&lt;/span&gt;
    &lt;span class="n"&gt;vm&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;goja&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error parsing JS file: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Parsed JavaScript file: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Open the zip file&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Iterate through the files in the zip archive&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JavaScript files in the zip archive:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Use filepath.Ext to check the file extension&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"data/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;".js"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c"&gt;// Exit after processing the first .js file so we don't end up printing a gazillion lines when testing&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Example usage&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Please provide the path to the zip file as an argument."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;SUPRISE. &lt;code&gt;window is not defined&lt;/code&gt; might be a familiar error. Basically goja runs an EMCA runtime. &lt;code&gt;window&lt;/code&gt; is browser context and sadly unavailable.&lt;/p&gt;

&lt;h2&gt;
  
  
  ACTUALLY Parse the JS
&lt;/h2&gt;

&lt;p&gt;I went through a few issues at this point. Including not being able to return data because it's a top level JS file.&lt;/p&gt;

&lt;p&gt;Long story short, we need to modify the contents of the files before loading them into the runtime.&lt;/p&gt;

&lt;p&gt;Let's modify the &lt;code&gt;main.go&lt;/code&gt; file;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reConfig&lt;/code&gt;: A regex that matches any assignment of the form &lt;code&gt;window.someVariable = {&lt;/code&gt; and replaces it with &lt;code&gt;var data = {&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reArray&lt;/code&gt;: A regex that matches any assignment of the form &lt;code&gt;window.someObject.someArray = [&lt;/code&gt; and replaces it with &lt;code&gt;var data = [&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Extracting &lt;code&gt;data&lt;/code&gt;: Running the script, we use &lt;code&gt;vm.Get("data")&lt;/code&gt; to retrieve the value of the data variable from the JavaScript context.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"archive/zip"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io/ioutil"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"path/filepath"&lt;/span&gt;
    &lt;span class="s"&gt;"regexp"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/dop251/goja"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Open the file inside the zip&lt;/span&gt;
    &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Read the contents of the file&lt;/span&gt;
    &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ioutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Regular expressions to replace specific patterns&lt;/span&gt;
    &lt;span class="n"&gt;reConfig&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;regexp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustCompile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`window\.\w+\s*=\s*{`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reArray&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;regexp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustCompile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`window\.\w+\.\w+\.\w+\s*=\s*\[`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Replace patterns in the content&lt;/span&gt;
    &lt;span class="n"&gt;processedContents&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplaceAllStringFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"var data = {"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;processedContents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reArray&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplaceAllStringFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processedContents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"var data = ["&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c"&gt;// Parse the JavaScript file using goja&lt;/span&gt;
    &lt;span class="n"&gt;vm&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;goja&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processedContents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error parsing JS file: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Retrieve the value of the 'data' variable from the JavaScript context&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No data variable found in the JS file"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Output the parsed data&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Processed JavaScript file: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Data extracted: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Open the zip file&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Iterate through the files in the zip archive&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Check if the file is in the /data directory and has a .js extension&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"data/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;".js"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c"&gt;// Exit after processing the first .js file so we don't end up printing a gazillion lines when testing&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Example usage&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Please provide the path to the zip file as an argument."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;Hurrah. Assuming I didn't muck up the copypaste into this post, you should now see a rather ugly print of the struct data from Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON would be nice
&lt;/h2&gt;

&lt;p&gt;Edit the &lt;code&gt;main.go&lt;/code&gt; file to marshall the JSON output.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;value.Export()&lt;/code&gt; to get the data from the struct&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;json.MarshallIndent()&lt;/code&gt; for pretty printed JSON (use &lt;code&gt;json.Marshall&lt;/code&gt; if you want to minify the output).&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"archive/zip"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io/ioutil"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"path/filepath"&lt;/span&gt;
    &lt;span class="s"&gt;"regexp"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/dop251/goja"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Open the file inside the zip&lt;/span&gt;
    &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Read the contents of the file&lt;/span&gt;
    &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ioutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// deprecated :/&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Regular expressions to replace specific patterns&lt;/span&gt;
    &lt;span class="n"&gt;reConfig&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;regexp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustCompile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`window\.\w+\s*=\s*{`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reArray&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;regexp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustCompile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`window\.\w+\.\w+\.\w+\s*=\s*\[`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Replace patterns in the content&lt;/span&gt;
    &lt;span class="n"&gt;processedContents&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplaceAllStringFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"var data = {"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;processedContents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reArray&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplaceAllStringFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processedContents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"var data = ["&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c"&gt;// Parse the JavaScript file using goja&lt;/span&gt;
    &lt;span class="n"&gt;vm&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;goja&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processedContents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error parsing JS file: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Retrieve the value of the 'data' variable from the JavaScript context&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No data variable found in the JS file"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Convert the data to a Go-native type&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Marshal the Go-native type to JSON&lt;/span&gt;
    &lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarshalIndent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"  "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error marshalling data to JSON: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Output the JSON data&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zipFilePath&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Open the zip file&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zipFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Iterate through the files in the zip archive&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Check if the file is in the /data directory and has a .js extension&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"data/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;".js"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c"&gt;// Exit after processing the first .js file&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Example usage&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Please provide the path to the zip file as an argument."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;zipFilePath&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zipFilePath&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;That's it!&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;p&gt;go run main.go twitter.zip&lt;/p&gt;

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

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accountId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Luke ✨"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"userName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lukeocodes"&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Open source&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;I'll be open sourcing a lot of this work so that others who want to parse the data from the archive, can store it how they like.&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
      <category>series</category>
    </item>
    <item>
      <title>The End of X (Or, It Should Be): How Elon Killed Twitter</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Wed, 07 Aug 2024 16:41:21 +0000</pubDate>
      <link>https://dev.to/lukeocodes/the-end-of-x-or-it-should-be-how-elon-killed-twitter-4586</link>
      <guid>https://dev.to/lukeocodes/the-end-of-x-or-it-should-be-how-elon-killed-twitter-4586</guid>
      <description>&lt;p&gt;When Elon Musk announced his intention to purchase Twitter in April 2022, he did so with grand promises. He pledged to tackle the platform's pervasive bot problem, ensure free speech, and improve overall user experience. However, as the dust settles, it's clear that these promises remain largely unfulfilled. Instead, the transformation of Twitter into "X" has introduced new issues, exacerbated old ones, and left users questioning the platform's future.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bot Problem: Worse Than Ever
&lt;/h2&gt;

&lt;p&gt;One of Musk's primary reasons for acquiring Twitter was his desire to eliminate spam bots, which he claimed plagued the platform. In 2022, Musk estimated that bots constituted 20% or more of Twitter accounts, significantly higher than Twitter's own estimates of less than 5%. This discrepancy became a central point of contention during the acquisition process, with Musk demanding more transparency from Twitter's then-management.&lt;/p&gt;

&lt;p&gt;Despite these claims, recent studies suggest that the bot problem has not improved under Musk's leadership. Data from CHEQ, a cybersecurity firm, revealed that invalid traffic from bots on Twitter increased significantly in 2023. Organic traffic showed an invalid rate of 10.43%, nearly double the 5.13% from paid traffic. This increase indicates that not only has Musk failed to address the bot issue, but it has also worsened since his takeover.&lt;/p&gt;

&lt;p&gt;Moreover, researchers have pointed out that accurately identifying bots remains a significant challenge due to the lack of a universally accepted definition. Tools like Botometer, which attempt to classify accounts based on bot-like behavior, have shown inconsistent results. For instance, Musk's own account has oscillated between high and low bot-like scores depending on the day. This inconsistency underscores the difficulty in differentiating between human and bot accounts, especially when many bots can mimic human interactions convincingly.&lt;/p&gt;

&lt;p&gt;Further complicating the issue, researchers from University College London have identified millions of bots on Twitter, most of which are malicious. These bots can execute simple yet harmful activities, such as spamming and spreading misinformation. Despite the evidence, Twitter has shown limited proactive measures in addressing these findings, likely due to the potential loss of user count that comes with bot removal.&lt;/p&gt;

&lt;p&gt;The bot problem on Twitter has not only persisted but also appears to have intensified under Musk's ownership. The complexities in accurately identifying and mitigating bots, combined with Twitter's apparent lack of urgency in addressing the issue, suggest that the platform's bot problem is far from being resolved​ (&lt;a href="https://www.cpomagazine.com/cyber-security/we-checked-elon-musks-claims-about-twitter-bots-heres-what-we-found/" rel="noopener noreferrer"&gt;CPO Magazine&lt;/a&gt;)​ (&lt;a href="https://techmonitor.ai/policy/big-tech/twitter-bots-why-so-hard-find-out-who-real" rel="noopener noreferrer"&gt;Tech Monitor&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Speech or Free for All?
&lt;/h2&gt;

&lt;p&gt;Musk's championing of "free speech" has been another cornerstone of his vision for Twitter. However, his interpretation of free speech has led to the platform becoming a haven for hate speech and dangerous misinformation. By rolling back moderation policies and restoring previously banned accounts, Musk has allowed users to propagate harmful content under the guise of free expression.&lt;/p&gt;

&lt;p&gt;When Musk took over Twitter, one of his first moves was to overhaul the content moderation policies. This included reinstating previously banned accounts and scaling back on efforts to curb misinformation and hate speech. Musk's assertion that "Twitter cannot become a free-for-all hellscape" has not been reflected in the platform's reality​ (&lt;a href="https://digitalplanet.tufts.edu/musk-monitor-under-musk-hate-speech-is-rising/" rel="noopener noreferrer"&gt;Digital Planet&lt;/a&gt;)​ (&lt;a href="https://www.businessinsider.com/elon-musk-free-speech-twitter-hate-disinformation-experts-2022-4" rel="noopener noreferrer"&gt;Business Insider&lt;/a&gt;). The result has been a significant uptick in harmful content, much of which targets marginalized communities.&lt;/p&gt;

&lt;p&gt;The data tells a troubling story. Researchers found that the daily average usage of hate keywords nearly doubled after Musk's acquisition of Twitter​ (&lt;a href="https://phys.org/news/2023-04-analysis-speech-significantly-twitter.html" rel="noopener noreferrer"&gt;Phys News&lt;/a&gt;). This rise in hate speech is particularly concerning given the platform's influence on public discourse. Anti-Semitic, ethnic hate speech, and anti-LGBTQ+ mentions have all seen marked increases. For example, anti-Semitic mentions rose by over 50% in the weeks following Musk's takeover​ (&lt;a href="https://digitalplanet.tufts.edu/musk-monitor-under-musk-hate-speech-is-rising/" rel="noopener noreferrer"&gt;Digital Planet&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Moreover, Musk's approach to free speech has emboldened extremists and conspiracy theorists. By allowing such content to flourish, he has effectively created an environment where misinformation spreads unchecked. This has had real-world consequences, including unrest and violence in places like the UK, where Musk's own posts have been linked to inflammatory incidents​ (&lt;a href="https://ca.finance.yahoo.com/news/few-twitter-posts-elon-musk-155955459.html" rel="noopener noreferrer"&gt;Yahoo Finance&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Human rights organizations and digital rights advocates have expressed deep concerns over these changes. Amnesty International and Human Rights Watch have both warned that Musk's policies could lead to a proliferation of hate speech and misinformation, making the platform unsafe for many users​ (&lt;a href="https://www.businessinsider.com/elon-musk-free-speech-twitter-hate-disinformation-experts-2022-4" rel="noopener noreferrer"&gt;Business Insider&lt;/a&gt;). The loosening of content moderation not only affects Twitter but sets a precedent for other social media platforms, potentially lowering the bar for acceptable speech across the internet.&lt;/p&gt;

&lt;p&gt;Musk's version of "free speech" has turned Twitter into a battleground of conflicting narratives, where harmful and false information can spread with little oversight. This shift has significant implications for the platform's role in shaping public opinion and political discourse, raising questions about the responsibilities of social media giants in moderating content. As we move forward, the challenge will be finding a balance between protecting free speech and ensuring the platform does not become a breeding ground for hate and misinformation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fact-Checking and API Access: A Broken Promise
&lt;/h2&gt;

&lt;p&gt;Under previous management, Twitter supported fact-checking initiatives and offered free API access to developers, enabling the creation of tools to combat misinformation and enhance user experience. This support allowed platforms like Polititweet to record and analyse Twitter history, providing a critical resource for researchers and fact-checkers. However, since Elon Musk's acquisition, these supportive measures have been dismantled, and the consequences have been significant.&lt;/p&gt;

&lt;p&gt;One of the most contentious changes under Musk's leadership has been the commercialisation of Twitter's API. Previously, researchers and developers could access the API for free, which was crucial for monitoring and counteracting misinformation. This access enabled projects that tracked harassment, analysed disinformation networks, and supported various public interest initiatives. The decision to monetise API access, with costs reaching up to $42,000 per month, has effectively locked out many academic and non-profit entities that relied on this data​ (&lt;a href="https://www.politifact.com/article/2023/oct/23/how-elon-musk-ditched-twitters-safeguards-and-prim/" rel="noopener noreferrer"&gt;PolitiFact&lt;/a&gt;)​ (&lt;a href="https://www.fastcompany.com/91040397/under-elon-musk-x-is-denying-api-access-to-academics-who-study-misinformation" rel="noopener noreferrer"&gt;Fast Company&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For instance, Polititweet, a platform that tracked and archived political tweets, was forced to cease operations in April 2023 when they were denied API access. This removal has hindered the ability of researchers to track the spread of misinformation and hold public figures accountable for their statements​ (&lt;a href="https://www.poynter.org/ifcn/2023/twitter-is-removing-free-api-access-and-no-one-is-excited/" rel="noopener noreferrer"&gt;Poynter&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The loss of free API access has broader implications for media and public trust. Twitter has historically been a valuable source for journalists and researchers, providing real-time data and insights. However, the platform's shift towards a pay-to-play model has degraded its reliability. Verified information is harder to find, while misinformation is given an undue platform due to the prioritisation of content from paying users.&lt;/p&gt;

&lt;p&gt;This shift has allowed individuals and entities to amplify false information with minimal oversight. For example, during the recent riots in the UK, Nigel Farage falsely claimed on live TV that a migrant was responsible for the murder of three young children, when in reality, the perpetrator was a British man born in Wales. Such incidents underscore the dangers of unchecked misinformation and the role Twitter now plays in spreading it​ (&lt;a href="https://www.politifact.com/article/2023/oct/23/how-elon-musk-ditched-twitters-safeguards-and-prim/" rel="noopener noreferrer"&gt;PolitiFact&lt;/a&gt;)​ (&lt;a href="https://www.poynter.org/ifcn/2023/twitter-is-removing-free-api-access-and-no-one-is-excited/" rel="noopener noreferrer"&gt;Poynter&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Another significant change has been the transformation of the blue check mark system. Previously, the blue check mark was a symbol of verified identity and credibility. Under Musk's management, it became a subscription service, allowing anyone willing to pay $8 a month to obtain a blue check mark. This change has led to rampant impersonation and a surge in misinformation. Accounts spreading false information can now appear legitimate, further eroding public trust​ (&lt;a href="https://www.politifact.com/article/2023/oct/23/how-elon-musk-ditched-twitters-safeguards-and-prim/" rel="noopener noreferrer"&gt;PolitiFact&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Elon Musk's tenure has fundamentally altered Twitter's role in information dissemination. The removal of free API access and the commodification of verification have stifled innovation, hindered research, and allowed misinformation to flourish. As a result, Twitter is no longer a reliable source for accurate information, posing significant challenges for media outlets and researchers alike. The platform's future as a credible information source remains in jeopardy, highlighting the need for robust oversight and regulation in the digital information space.&lt;/p&gt;

&lt;h2&gt;
  
  
  The End is Nigh
&lt;/h2&gt;

&lt;p&gt;There is substantial evidence indicating that Twitter's real-user numbers are in decline while bot activity is on the rise under Elon Musk's leadership.&lt;/p&gt;

&lt;p&gt;Twitter is projected to lose over 32 million users by the end of 2024. This decline is attributed to technical issues, offensive content, and a general frustration with the platform's changes under Musk's ownership. Specifically, Twitter's user base is expected to decrease by 5.1% in 2024, following a 3.9% drop in 2023​​ (&lt;a href="https://www.itworldcanada.com/article/twitter-to-lose-more-than-32-million-users-worldwide-by-2024-report/517877" rel="noopener noreferrer"&gt;IT World Canada&lt;/a&gt;)​ (&lt;a href="https://www.oberlo.com/statistics/twitter-user-growth" rel="noopener noreferrer"&gt;Oberlo&lt;/a&gt;). Analysts point out that this exodus is driven by Musk's erratic behavior, increased hate speech, and a deteriorating user experience​​ (&lt;a href="https://absolutelymaybe.plos.org/2023/06/07/17-studies-plus-advertising-data-map-out-twitters-decline/" rel="noopener noreferrer"&gt;Absolutely Maybe&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;While the real-user numbers decline, bot activity has shown signs of increasing. Studies have highlighted that Musk's efforts to combat bots have not only fallen short but, in some cases, may have worsened the situation. Bots continue to play a significant role on the platform, particularly in spreading misinformation​​ (&lt;a href="https://www.businessofapps.com/data/twitter-statistics/" rel="noopener noreferrer"&gt;Business of Apps&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Elon Musk has hinted at transforming Twitter into a broader application platform, potentially resembling China's WeChat, integrating services like messaging, payments, and more. His goal of making "X" a super-app reflects a significant shift from Twitter's original purpose​​. Additionally, Musk's plans include a massive increase in users and revenue, targeting $26.4 billion in annual revenue by 2028 and reaching 931 million users​​ (&lt;a href="https://sfstandard.com/2024/08/05/timeline-elon-musk-twitter-ownership/" rel="noopener noreferrer"&gt;The San Francisco Standard&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Hints of more drastic changes or even the closure of Twitter as we know it have surfaced. Musk's decisions, including rebranding Twitter to "X" and relocating the company's headquarters from California to Texas due to regulatory and political disagreements, suggest a potential overhaul or shutdown of the current platform​​ (&lt;a href="https://sfstandard.com/2024/08/05/timeline-elon-musk-twitter-ownership/" rel="noopener noreferrer"&gt;The San Francisco Standard&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;These trends are alarming, especially considering Twitter's role as a major platform for public discourse. The rise in bots and decline in genuine user engagement could further erode trust in the platform, making it a less reliable source for information and news. Media outlets and the public may need to reconsider how much they rely on Twitter for accurate and timely information, as the platform's changes under Musk continue to undermine its credibility and functionality.&lt;/p&gt;

&lt;p&gt;Furthermore, Musk's contentious relationship with advertisers has significantly affected Twitter's revenue. Since his takeover, monthly ad revenue has declined by at least 55% year-over-year, with a particularly steep drop of 71% in December 2023. In response to this exodus, Musk has accused the Global Alliance for Responsible Media (GARM) of orchestrating mass boycotts and has even sued several major advertisers, alleging they illegally conspired to harm his company. Despite efforts to win back advertisers by improving brand safety controls, the platform's reputation and revenue continue to suffer from these disputes (&lt;a href="https://www.reuters.com/technology/ad-spending-twitter-falls-by-over-70-dec-data-2023-01-25/" rel="noopener noreferrer"&gt;Reuters&lt;/a&gt;) (&lt;a href="https://www.thewrap.com/x-ad-revenue-decline-elon-musk/" rel="noopener noreferrer"&gt;The Wrap&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The ongoing war with advertisers highlights Musk's struggle to maintain a viable business model amid widespread criticism and declining user trust. The legal battles and drastic measures reflect his desperate attempts to salvage the platform, but they also underscore the broader challenges Twitter faces under his leadership ((AccessWDUN)[&lt;a href="https://accesswdun.com/article/2024/8/1467227/elon-musk-x-sues-advertisers%5D" rel="noopener noreferrer"&gt;https://accesswdun.com/article/2024/8/1467227/elon-musk-x-sues-advertisers]&lt;/a&gt;).&lt;/p&gt;

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

&lt;p&gt;Twitter's transformation under Elon Musk has led to a steep decline in real-user numbers and a significant rise in bot activity. With projections indicating a loss of over 32 million users by the end of 2024 and ad revenue plummeting by as much as 78% in December 2023, the platform's viability as a business is under serious threat. If the current rate of decline continues, Twitter may become unsustainable within the next few years.&lt;/p&gt;

&lt;p&gt;As users and advertisers flee the platform, several alternatives have emerged. Some of the most prominent include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mastodon: A decentralized social network that allows users to create their own communities.&lt;/li&gt;
&lt;li&gt;Bluesky: A project initiated by Twitter's former CEO Jack Dorsey, aiming to develop a decentralized social media protocol.&lt;/li&gt;
&lt;li&gt;Threads: Launched by Meta (Facebook's parent company), Threads aims to integrate with Instagram to offer a new social networking experience.&lt;/li&gt;
&lt;li&gt;Discord: While primarily a chat application, Discord offers robust community features that many former Twitter users find appealing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those looking to migrate their data from Twitter, tools and platforms vary in their support. Some services like Mastodon offer partial migration tools to help users import their tweets and followers. However, comprehensive migration solutions are limited, and users often need to manually save and transfer their data.&lt;/p&gt;

&lt;p&gt;In summary, Twitter's decline under Musk's leadership, characterized by user loss and increasing bot prevalence, indicates a potentially bleak future for the platform. As users seek alternatives, new and existing social networks are stepping up to fill the void, albeit with varying degrees of data migration support.&lt;/p&gt;

&lt;p&gt;Time to leave?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Me: &lt;br&gt;
According to this: &lt;a href="https://dev.to/terms"&gt;https://dev.to/terms&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can i swear about someone in my article? I'd like to end it with a message about Elon Musk and his interference with UK politics.&lt;/p&gt;

&lt;p&gt;ChatGPT: &lt;br&gt;
According to the terms of use for dev.to, using offensive is prohibited. The terms state:&lt;/p&gt;

&lt;p&gt;"Please refrain from name-calling and ad hominem attacks when discussing other users, people, or companies. Avoid using offensive or inappropriate language. If you’re unsure whether a word is offensive, it probably is. If you’re confident it isn’t, someone else might disagree."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;:(&lt;/p&gt;

&lt;p&gt;As usual with my posts. I use AI as a writing aide because "Luke no write good". I use it as a copywriter, to turn my ramblings and crappy source notes into something you can read.&lt;/p&gt;

</description>
      <category>news</category>
      <category>disinformation</category>
      <category>security</category>
    </item>
    <item>
      <title>Just Surviving: Working Remotely for Beginners</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Sun, 04 Aug 2024 10:56:11 +0000</pubDate>
      <link>https://dev.to/lukeocodes/just-surviving-working-remotely-for-beginners-342n</link>
      <guid>https://dev.to/lukeocodes/just-surviving-working-remotely-for-beginners-342n</guid>
      <description>&lt;p&gt;Following on from my last post where I talked about how difficult working remotely can be, today I am posting some of my advice on how to maintain a positive you-work relationship, and healthy work-life balance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find Somewhere to Hide
&lt;/h2&gt;

&lt;p&gt;First and foremost, having a dedicated workspace can do wonders for your productivity and mindset. Even if you don't have a separate room, find a corner that you can make your own. This space should be free from distractions and solely for work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Off Your Butt
&lt;/h2&gt;

&lt;p&gt;One of the perks of working remotely is flexibility, but too much flexibility can lead to chaos. Set a consistent schedule that includes starting and ending your workday at the same time each day. Include regular breaks to avoid burnout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Dressed You Lazy Sod
&lt;/h2&gt;

&lt;p&gt;It might be tempting to stay in your pajamas all day, but getting dressed as if you're going to the office can help put you in the right mindset. It doesn't have to be formal, but changing out of your sleepwear can signal to your brain that it's time to get to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Technology Wisely
&lt;/h2&gt;

&lt;p&gt;Leverage tools that enhance communication and productivity. Applications like Clockwise can help you stay connected with your team and manage your tasks efficiently. Stay away from your video games and megadrives! /s&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay Connected
&lt;/h2&gt;

&lt;p&gt;Working remotely can be isolating. Make an effort to stay connected with your colleagues. Regular check-ins, virtual coffee breaks, and team meetings can help maintain a sense of camaraderie and keep you in the loop.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/lukeocodes" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F133562%2F7ca76112-5580-4245-8a48-b24bf6f4fb51.jpg" alt="lukeocodes"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/lukeocodes/i-love-working-remotely-even-though-it-sucks-at-times-4g41" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;I Love Working Remotely, Even Though It Sucks at Times&lt;/h2&gt;
      &lt;h3&gt;@lukeocodes 🕹👨‍💻 ・ Apr 23&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#workfromhome&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#mentalhealth&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Set Boundaries
&lt;/h2&gt;

&lt;p&gt;It's easy to let work spill into your personal time when you work from home. Set clear boundaries and communicate them with your household members and your team. Let everyone know when you're available and when you're not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prioritize Self-Care
&lt;/h2&gt;

&lt;p&gt;Your mental and physical health should always come first. Take regular breaks, go for walks, and make time for activities that help you unwind. Remember, a healthy mind and body are crucial for sustained productivity. Put. These. In. Your. Work. Calendar. Untitled if necessary. Build them into your day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep Learning and Growing
&lt;/h2&gt;

&lt;p&gt;Use the extra time saved from commuting to invest in your personal and professional development. There are countless online courses and resources available to help you acquire new skills and stay competitive in your field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be Patient with Yourself
&lt;/h2&gt;

&lt;p&gt;Lastly, be patient. It might take some time to find what works best for you. Give yourself grace as you figure out this new way of working.&lt;/p&gt;

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

&lt;p&gt;Full transparency, my own words have been enhanced by the use of GPT because I have the writing skills of a 12-year-old. So sue me. But in all seriousness, finding the right balance and maintaining a positive relationship with your work when remote is key to thriving in this environment. With these tips, I hope you find it a bit easier to navigate the challenges and make the most of your remote work experience.&lt;/p&gt;

</description>
      <category>workfromhome</category>
      <category>mentalhealth</category>
    </item>
    <item>
      <title>I Love Working Remotely, Even Though It Sucks at Times</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Tue, 23 Apr 2024 12:35:48 +0000</pubDate>
      <link>https://dev.to/lukeocodes/i-love-working-remotely-even-though-it-sucks-at-times-4g41</link>
      <guid>https://dev.to/lukeocodes/i-love-working-remotely-even-though-it-sucks-at-times-4g41</guid>
      <description>&lt;p&gt;I love and hate remote working. I have a battery-life, and as time marches on I run low, needing human interaction to recharge my social battery. I used to love working at Vonage, because it had a London office and being a remote worker they'd pay for my expenses to get on the train and have face time with my colleagues. My old manager (Hi, Martyn!) once said that he believed I was the type of person that was happier being in the office.&lt;/p&gt;

&lt;p&gt;I am, he was right. But, I live in the arse-end of nowhere and also want to work on cutting-edge technology like that we get to work on at Deepgram. I can't have it both ways, and I'm comfortable being uncomfortable for the benefit of being able to give my family the start I didn't have (IYKYK).&lt;/p&gt;

&lt;p&gt;Not just me, but more people are working from home than ever before. Thanks, COVID (I was working from home before COVID, to be fair). This change has lots of benefits like no commute and being able to wear what you like. But it's not all perfect, as the truly toxic downside is the loneliness of missing the faces of your colleagues and work friends.&lt;/p&gt;

&lt;p&gt;Despite its advantages, remote work isn't without its problems. During the pandemic, the sudden shift from bustling office environments to quiet, sometimes solitary home offices left a lot of us (I couldn't visit our offices anymore - or anyone) feeling adrift in uncharted waters. While we'd eliminated the daily commute, we also lost the spontaneous conversations that spark ideas and the unplanned encounters that often led to innovation. For companies across the globe, the task became to not just to facilitate remote work but to reinvent the collaboration that once took place naturally in physical places.&lt;/p&gt;

&lt;p&gt;This collaboration was crucial not only for productivity but for the emotional and social wellbeing of colleagues. Understanding this is essential in allowing people to connect. Not just digitally, but emotionally and professionally as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rise of Working from Home
&lt;/h2&gt;

&lt;p&gt;A lot of people want to keep working from home after trying it. A survey by Buffer shows that nearly all remote workers would like to continue working this way at least some of the time (&lt;a href="https://buffer.com/state-of-remote-work/2021" rel="noopener noreferrer"&gt;Buffer's State of Remote Work 2021&lt;/a&gt;). While the perks are great, there's a big downside: feeling isolated.&lt;/p&gt;

&lt;p&gt;As companies worldwide adapted to public health requirements, remote work transitioned from a nice-to-have to an essential status almost overnight. This not only transformed how businesses operated, but also prompted a reevaluation of what constitutes a productive work environment. I remember my wife sitting at our kitchen table for 6-7 hours with a headset, answering angry calls from members of the public as she worked for a local government agency. Not exactly a productive work environment, but it meant she could work - then, the only requirement I guess.&lt;/p&gt;

&lt;p&gt;Previously, remote work was often seen as a perk offered by tech-savvy and trendy companies. However, now it's recognized as a normal and sometimes preferable way of working in all industries. Even my good friend, who works for the local Police service, works from home on his very encrypted and serious laptop.&lt;/p&gt;

&lt;p&gt;This widespread adoption has led to innovations in digital tools and communication technologies, pushing companies to invest in better software and hardware to keep their teams connected and productive. As the dust settles, it's clear that the landscape of work has been permanently altered, with remote work proving its value and sustainability beyond the circumstances that necessitated its rise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does Loneliness in Remote Work Look Like?
&lt;/h2&gt;

&lt;p&gt;When you work from home, it's easy to feel left out. You might miss out on the quick chats and office news. According to a report by Igloo Software, 70% of people working from home feel they're not as connected to what’s happening at work (&lt;a href="https://www.igloosoftware.com/resources/" rel="noopener noreferrer"&gt;Igloo Software’s 2021 Remote Work Insights&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;This feeling of disconnection isn't confined to missing out on social interactions or chats; it permeates the daily work routine. Remote workers often find themselves working odd hours to accommodate time zones or personal responsibilities, which can further deepen the sense of isolation. Without the physical cues and environmental context of an office setting, interpreting tone and intent in digital communications can become challenging, leading to misunderstandings or a feeling of being out of sync with the rest of the team.&lt;/p&gt;

&lt;p&gt;These challenges are compounded for new hires who have to integrate into teams without ever meeting their coworkers in person, relying solely on virtual introductions and online meetings to build relationships that once developed naturally in shared physical spaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Effects of Feeling Alone
&lt;/h2&gt;

&lt;p&gt;Feeling lonely isn’t just about feeling sad or left out. It can seriously affect your health. Research shows that being very lonely can be as bad for your health as smoking 15 cigarettes a day (&lt;a href="https://www.hrsa.gov/enews/past-issues/2019/january-17/loneliness-epidemic" rel="noopener noreferrer"&gt;Health Resources &amp;amp; Services Administration&lt;/a&gt;). It can make you stressed, anxious, and can even make it hard to think or make decisions.&lt;/p&gt;

&lt;p&gt;The result of these feelings of isolation extend beyond just the individual. They can ripple through an entire company, affecting team dynamics and overall performance. Remote workers who feel disconnected may struggle with motivation, finding it harder to get started on tasks or to prioritize their workload effectively. &lt;/p&gt;

&lt;p&gt;Overall, lack of engagement can lead to decreased productivity and can even influence team morale, as the disconnection felt by one member can subtly impact the group's dynamics. Furthermore, the psychological stress of loneliness can lead to increased absenteeism and higher turnover rates - Yes, people will leave if they feel lonely.&lt;/p&gt;

&lt;p&gt;Companies must recognize these potential risks and work to create a work environment that fosters personal connections and a sense of community, regardless of physical location. This is even more important when companies are both remote, and co-located.&lt;/p&gt;

&lt;p&gt;As someone with ADHD, it's critical - for me personally - that I find motivation in my work. Any occurence of this dysfunction can be crippling, and has risked my entire career on more than one occasion.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Can We Talk More?
&lt;/h2&gt;

&lt;p&gt;Talking and connecting with others is really important, especially when everyone is working from different places. Snatch every opportunity to meet in person, budgets/finance permitting.&lt;/p&gt;

&lt;p&gt;To counter the effects of isolation, our company employs a few different ways to encourage communication and foster a sense of community among team members. &lt;/p&gt;

&lt;p&gt;Slack serves as our most central hub for both immediate and asynchronous conversations, allowing team members to share updates, ask questions, and engage in light-hearted chat throughout the day.&lt;/p&gt;

&lt;p&gt;Slack reported that users are nearly twice as likely as non-Slack users to report that their sense of belonging improved while working from home (&lt;a href="https://slack.com/intl/en-gb/blog/collaboration/report-remote-work-during-coronavirus#:~:text=Slack%20users%C2%B9%20are%20nearly%20twice,of%20those%20who%20use%20Slack." rel="noopener noreferrer"&gt;Collaboration tools can help remote workers adapt - Slack&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Regular Catch-ups
&lt;/h3&gt;

&lt;p&gt;One good way to keep everyone connected is to have regular video calls. These can be every day or once a week. It's a chance for everyone to see each other and talk about how things are going, not just work stuff.&lt;/p&gt;

&lt;p&gt;Our use of &lt;a href="https://ro.am/" rel="noopener noreferrer"&gt;Roam as our virtual office space&lt;/a&gt; enables us to maintain the semblance of an office environment. Here, individuals can pop into each other’s virtual offices for quick face-to-face chats, or collaborate in real-time on team projects, maintaining a level of spontaneity and interaction that traditional remote work setups might lack. I love that my skip-level makes us move to a meeting room, as our individual offices don't have the option for video (What's up with that, Roam?!).&lt;/p&gt;

&lt;h3&gt;
  
  
  Virtual Hangout Spots
&lt;/h3&gt;

&lt;p&gt;Setting up a place online where people can chat casually, like they would in an office kitchen, can help too. This could be a chat channel where no work talk is allowed. Just a spot to share a funny picture or talk about your weekend.&lt;/p&gt;

&lt;p&gt;I hoped &lt;a href="https://discord.gg/deepgram" rel="noopener noreferrer"&gt;our Discord channel could extend this community&lt;/a&gt; beyond just our team members,  to include our customers. I created a vibrant forum where we (some team and customers) exchange ideas, and immediate feedback can be sought to improve our products. This platform not only helps in supporting our products but also in building a more connected community of users and developers, and helps team members feel more connected to humans using our products.&lt;/p&gt;

&lt;h3&gt;
  
  
  Meet as Often as Possible
&lt;/h3&gt;

&lt;p&gt;Our commitment to communication and community is further enabled by our annual in-person company offsite (Two trips to Mexico in 14 months? Yes please!). This event provides a unique opportunity for team members to connect, discuss company-wide strategies, and strengthen our relationships away from our projects and webcams. Additionally, we organize team offsites that, while less frequent, are immeasurably valuable for deeper team bonding and strategic planning. All these offsites are crucial in aligning our goals, refreshing our spirits, and breaking the monotony of talking into a cube of plastic, glass, image sensor, and circuitry.&lt;/p&gt;

&lt;p&gt;Through these varied channels and opportunities, we aim to create a comprehensive communication ecosystem that not only addresses the challenges of remote work loneliness but actively promotes an inclusive and engaging work environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Turn Your Camera On
&lt;/h3&gt;

&lt;p&gt;Gentle encouragement on camera use during meetings can significantly enhance the feeling of connection among team members. Seeing each other's expressions and gestures can make communication more natural and engaging, helping to bridge the gap that physical distance creates. It can also help neuro-divergent folks understand your tone, and people with hidden disabilities such as hearing loss lip read. As, just like written communication, audio can miss a lot of physical cues about tone and emotion. But, it’s always important to approach this practice with sensitivity to individual preferences and circumstances.&lt;/p&gt;

&lt;p&gt;Recognize that not everyone may be comfortable having their camera on at all times. Factors such as personal comfort, the environment at home, or even internet limitations can influence this decision. To accommodate this, be flexible. Encourage having cameras on to foster a more connected and inclusive meeting experience, but also emphasize that it is entirely optional.&lt;/p&gt;

&lt;p&gt;This approach ensures that all team members feel they can choose to engage in a way that feels most comfortable to them, without feeling pressured. By prioritizing comfort, create a supportive atmosphere that respects personal boundaries and promotes a positive experience.&lt;/p&gt;

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

&lt;p&gt;Talking to each other is super important in remote work. It helps everyone feel part of the team and stops us from feeling too lonely. Companies need to think about how they can help people keep chatting, not just about work but about fun stuff too. This keeps everyone feeling happy and connected.&lt;/p&gt;

&lt;p&gt;And let's face it, working alone at home can make you do funny things. I've started talking to my child's toy as if it's my colleague—lost my rubber duck, so now I'm rubber duck debugging with a plastic peppa-pig! Turns out, it's not only good for coding problems, but a great punishment for naughty 3-year olds. Peppa's coming to work with Daddy today.&lt;/p&gt;

&lt;p&gt;Full transparency, my own words have been occationally enhanced by the use of GPT because I have the writing skills of a 12 year old. So sue me.&lt;/p&gt;

</description>
      <category>workfromhome</category>
      <category>mentalhealth</category>
    </item>
    <item>
      <title>Introducing Deepgram Starter Apps</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Tue, 18 Apr 2023 11:15:06 +0000</pubDate>
      <link>https://dev.to/deepgram/introducing-deepgram-starter-apps-33m5</link>
      <guid>https://dev.to/deepgram/introducing-deepgram-starter-apps-33m5</guid>
      <description>&lt;p&gt;Deepgram aims to make world-class language AI available to any developer through just an API call. With today’s launch of Deepgram Starter Apps, it’s even easier to start building applications which take real-time and prerecorded audio data and transform them into transcripts enriched with natural language understanding metadata.&lt;/p&gt;

&lt;p&gt;Put simply, we help developers build quickly because we’ve already taken care of integrating Deepgram into Starter Apps. Whether you've been coding for decades or just passed CS50, Deepgram's Starter apps provide all users with a seamless and efficient onboarding experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript Starter
&lt;/h3&gt;

&lt;p&gt;The JavaScript Starter is the first arrival from the collection and provides developers with a pre-built Deepgram integration on Node.js, with a React frontend, to quickly get started with the Deepgram platform. With the JavaScript Starter, all developers can rapidly explore what Deepgram's platform can do.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://github.com/deepgram-starters/deepgram-javascript-starters" rel="noopener noreferrer"&gt;JavaScript Starter Apps on Github&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python Starter
&lt;/h3&gt;

&lt;p&gt;The Python Starter is our follow up to the collection and provides developers with a pre-built Flask integration with Deepgram. Presently, there is a static frontend to interact with the Flask server.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://github.com/deepgram-starters/deepgram-python-starters" rel="noopener noreferrer"&gt;Python Starter Apps on Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  New transcription models included
&lt;/h2&gt;

&lt;p&gt;Deepgram introduces two new models this week. These are available to try out immediately in our new Starter applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deepgram Nova
&lt;/h3&gt;

&lt;p&gt;On April 13th, 2023, we announced Deepgram Nova, a cutting-edge Automatic Speech Recognition (ASR) system. Deepgram Nova achieves unprecedented performance, beating competitors in speed, accuracy, and efficiency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Whisper Cloud
&lt;/h3&gt;

&lt;p&gt;Alongside Deepgram Nova’s release, we announced Deepgram Whisper Cloud. Following last month's release of OpenAI's Whisper API, we noticed its popularity, despite its limitations. We've developed our own fully managed Whisper API to address those limitations.&lt;/p&gt;

&lt;p&gt;Read more about our &lt;a href="https://blog.deepgram.com/nova-speech-to-text-whisper-api/" rel="noopener noreferrer"&gt;Deepgram Nova and Deepgram Whisper Cloud release here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read more
&lt;/h2&gt;

&lt;p&gt;Read more about our new opensource projects in our latest post, &lt;a href="https://blog.deepgram.com/introducing-the-deepgram-starter-apps/" rel="noopener noreferrer"&gt;Introducing Deepgram Starter Apps&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>news</category>
      <category>ai</category>
      <category>python</category>
      <category>deepgram</category>
    </item>
    <item>
      <title>I'm Luke and I pledge to help close the gender gap in 2022!</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Tue, 08 Mar 2022 22:02:36 +0000</pubDate>
      <link>https://dev.to/lukeocodes/im-luke-and-i-pledge-to-help-close-the-gender-gap-in-2022-33dj</link>
      <guid>https://dev.to/lukeocodes/im-luke-and-i-pledge-to-help-close-the-gender-gap-in-2022-33dj</guid>
      <description>&lt;p&gt;Much like &lt;a href="https://dev.to/lukeocodes/i-m-luke-and-i-support-women-in-tech-23gk"&gt;last year&lt;/a&gt;, I intend to give my space to those women who inspired me in the last year.&lt;/p&gt;

&lt;p&gt;Check out the work and content of these amazing humans, and leaders. Give them a follow on Twitter if you want to fill your feed with great content, positive takes, and cool tech tips.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://twitter.com/whitep4nth3r" rel="noopener noreferrer"&gt;Salma Alam-Naylor&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;One of the literal few people I speak to regularly about ALL of the frustrations in tech, parenting, tech, tech, and tech Twitter. Not just a great human, she is also an amazing leader. While &lt;a href="https://www.twitch.tv/whitep4nth3r" rel="noopener noreferrer"&gt;streaming on Twitch&lt;/a&gt;, she could pick any old project to wow us with. But, continually pulls out projects which elevate women in tech. Follow for tech tips, memes, and an amazing stream!&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__492757"&gt;
    &lt;a href="/whitep4nth3r" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F492757%2F6440ce47-9fbc-4865-a283-8aab7ea11580.png" alt="whitep4nth3r image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/whitep4nth3r"&gt;Salma Alam-Naylor&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/whitep4nth3r"&gt;I write code for your entertainment.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  &lt;a href="https://twitter.com/nhcarrigan" rel="noopener noreferrer"&gt;Naomi Carrigan&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Naomi is a career switching &lt;del&gt;future&lt;/del&gt; open source star. When I first met Naomi she was still looking for her first full-time tech role, and now she is an engineer for one of the most important free spaces for folks looking to get into tech, &lt;a href="https://www.freecodecamp.org/" rel="noopener noreferrer"&gt;freecodecamp&lt;/a&gt;. Follow for great takes, great content, and great open source projects!&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1479920158361587712-226" src="https://platform.twitter.com/embed/Tweet.html?id=1479920158361587712"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1479920158361587712-226');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1479920158361587712&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://twitter.com/TonyaSims" rel="noopener noreferrer"&gt;Tonya Sims&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Hall-of-Fame basketball player–turned Python Engineer and Developer Advocate. Tonya and I just missed each other at Vonage, but now I get to call her my colleague, anyway! Tonya inspires me with how she approaches work, is always super positive, and is a great advocate for the Python community. Follow for great Python content!&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__336317"&gt;
    &lt;a href="/tonyasims" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F336317%2F5cc4ee58-abb2-49c6-ba9c-ac36e290405b.jpg" alt="tonyasims image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/tonyasims"&gt;Tonya Sims&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/tonyasims"&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  &lt;a href="https://twitter.com/BekahHW" rel="noopener noreferrer"&gt;Bekah Hawrot Weigel&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Bekah just joined us at Deepgram and I'm so excited to see her build our community there. No sooner had Bekah qualified from FlatIron than she had made a more positive impact on her tech community than I have in my decade in tech. It isn't surprising when you see how she tackles work and community projects. Follow for community, code, and life content. Also check out &lt;a href="https://twitter.com/VirtualCoffeeIO" rel="noopener noreferrer"&gt;Virtual Coffee&lt;/a&gt;, a virtual meetup, community, and podcast, for developers at all stages of their careers.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__345658"&gt;
    &lt;a href="/bekahhw" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F345658%2Fa72b6b8b-b954-47fb-8919-ab380905f26b.jpg" alt="bekahhw image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/bekahhw"&gt;BekahHW&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/bekahhw"&gt;Hey! I'm Bekah. I'm a career-changer. Bootcamp grad. Dev. Writer. Keynote Speaker. Mom to 4 kids. Creator, Maintainer, Podcast co-host: VirtualCoffee.io | Developer Experience Lead, @OpenSauced&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  &lt;a href="https://twitter.com/sandra_rodgers_" rel="noopener noreferrer"&gt;Sandra Rodgers&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Sandra is a former school teacher and now engineer who is now combining both worlds as a Developer Experience Engineer at Deepgram. She has also created some amazing content for Vue 3 which has helped me a ton (because I was being super lazy and avoiding Vue 3 stuff). Follow for great content and hopefully dog pics.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__312029"&gt;
    &lt;a href="/sandrarodgers" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F312029%2F73f50c87-158f-41ee-957e-4f8059a33df5.jpeg" alt="sandrarodgers image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/sandrarodgers"&gt;SandraRodgers&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/sandrarodgers"&gt;Former teacher turned developer. 

On the developer relations team at Deepgram, I get to teach and build stuff, blending both of my professional passions - education and development.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  &lt;a href="https://twitter.com/LoLoCoding" rel="noopener noreferrer"&gt;Lauren Lee&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Lauren was my manager at Vonage. She always inspired me by championing her colleagues needs before her own. She has &lt;a href="https://webelongpodcast.com/" rel="noopener noreferrer"&gt;an amazing podcast called We Belong Here&lt;/a&gt; that aims to interview career changers and folks that are diversifying the tech industry. Follow for great tech content, jealousy inducing beach pics, podcast announcements, and friendly takes.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__236316"&gt;
    &lt;a href="/lolocoding" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F236316%2Fce26420f-318e-4227-9c3c-595213d4fe8c.jpg" alt="lolocoding image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/lolocoding"&gt;Lauren Lee&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/lolocoding"&gt;An English teacher turned empathetic software engineer. 
Host of the #WebBelongHere podcast which celebrates the stories of folks who've entered tech via unconventional paths! &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  &lt;a href="https://twitter.com/Czajkowski" rel="noopener noreferrer"&gt;Laura Czajkowski&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Laura is hands down one of the most empathetic people I've had the pleasure to work with. She helped me in 2021 through difficult times with great advice and I'm so grateful to call her a friend. Follow for tweets about tech and industry content, and dog pics!&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__342897"&gt;
    &lt;a href="/czajkowski" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F342897%2Fd1fa245a-c3bf-432c-abed-cf1b1f419286.jpeg" alt="czajkowski image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/czajkowski"&gt;Laura Czajkowski&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/czajkowski"&gt;Head of Community Dragonfly, #OpenSource Advocate, #Community builder, Rugby Fan, Former Ubuntu Community &amp;amp; Loco Council&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  &lt;a href="https://twitter.com/lornajane" rel="noopener noreferrer"&gt;Lorna Mitchell&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Lorna was a colleague of mine at Nexmo, and later Vonage. She completely changed the way I approached equity and empathy. In how Lorna challenged and explained situations, to first hand advice, I learned so much from her. She also makes great content, and her blog is one of a few tech sites even in my bookmarks. Follow for tweets on open source and new content.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__343386"&gt;
    &lt;a href="/lornajane" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F343386%2F81561ca5-90e1-41c5-a67a-b7188a632627.png" alt="lornajane image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/lornajane"&gt;Lorna Jane Mitchell&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/lornajane"&gt;I'm a polyglot software developer and writer, currently working as a Developer Advocate and loving working with APIs!&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Last Year Me
&lt;/h2&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/lukeocodes" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F133562%2F7ca76112-5580-4245-8a48-b24bf6f4fb51.jpg" alt="lukeocodes"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/lukeocodes/i-m-luke-and-i-support-women-in-tech-23gk" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;I'm Luke, and I support women in tech.&lt;/h2&gt;
      &lt;h3&gt;@lukeocodes 🕹👨‍💻 ・ Mar 7 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#wecoded&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#womenintech&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>wecoded</category>
      <category>career</category>
      <category>womenintech</category>
    </item>
    <item>
      <title>I'm Luke, and I support women in tech.</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Sun, 07 Mar 2021 14:05:17 +0000</pubDate>
      <link>https://dev.to/lukeocodes/i-m-luke-and-i-support-women-in-tech-23gk</link>
      <guid>https://dev.to/lukeocodes/i-m-luke-and-i-support-women-in-tech-23gk</guid>
      <description>&lt;p&gt;Last year I wrote about things I advocated that we can all do to further equality in tech. I still strongly suggest those, so check it out. (Link at the end).&lt;/p&gt;

&lt;p&gt;This year, I'd rather give space to those in tech who have or do inspire me. &lt;/p&gt;

&lt;p&gt;So, do yourself a favor and check out the work and content of these amazing humans, and leaders.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://twitter.com/LoLoCoding" rel="noopener noreferrer"&gt;Lauren Lee&lt;/a&gt;
&lt;/h3&gt;


&lt;div class="ltag__user ltag__user__id__236316"&gt;
    &lt;a href="/lolocoding" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F236316%2Fce26420f-318e-4227-9c3c-595213d4fe8c.jpg" alt="lolocoding image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/lolocoding"&gt;Lauren Lee&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/lolocoding"&gt;An English teacher turned empathetic software engineer. 
Host of the #WebBelongHere podcast which celebrates the stories of folks who've entered tech via unconventional paths! &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  &lt;a href="https://twitter.com/lornajane" rel="noopener noreferrer"&gt;Lorna Mitchell&lt;/a&gt;
&lt;/h3&gt;


&lt;div class="ltag__user ltag__user__id__343386"&gt;
    &lt;a href="/lornajane" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F343386%2F81561ca5-90e1-41c5-a67a-b7188a632627.png" alt="lornajane image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/lornajane"&gt;Lorna Jane Mitchell&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/lornajane"&gt;I'm a polyglot software developer and writer, currently working as a Developer Advocate and loving working with APIs!&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  &lt;a href="https://twitter.com/theannalytical" rel="noopener noreferrer"&gt;Anna Lytical&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1253723511820034054-750" src="https://platform.twitter.com/embed/Tweet.html?id=1253723511820034054"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1253723511820034054-750');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1253723511820034054&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h3&gt;
  
  
  April Speight
&lt;/h3&gt;


&lt;div class="ltag__user ltag__user__id__455958"&gt;
    &lt;a href="/aprilspeight" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F455958%2F6a7d807f-2549-4617-8f47-02c71abbf133.png" alt="aprilspeight image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/aprilspeight"&gt;April Speight&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/aprilspeight"&gt;Sr. Cloud Advocate - Spatial Computing at Microsoft. You can often find me playing with holograms.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  &lt;a href="https://twitter.com/KimCrayton1" rel="noopener noreferrer"&gt;Kim Crayton&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1364956385285468165-326" src="https://platform.twitter.com/embed/Tweet.html?id=1364956385285468165"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1364956385285468165-326');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1364956385285468165&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h3&gt;
  
  
  Jo Franchetti
&lt;/h3&gt;


&lt;div class="ltag__user ltag__user__id__331818"&gt;
    &lt;a href="/thisisjofrank" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F331818%2Fe0094f5c-f975-4712-96df-a1f894459421.jpg" alt="thisisjofrank image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/thisisjofrank"&gt;Jo Franchetti&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/thisisjofrank"&gt;Jo is a developer advocate at Deno.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  &lt;a href="https://github.com/sdras" rel="noopener noreferrer"&gt;Sarah Drasner&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1368289251234697216-210" src="https://platform.twitter.com/embed/Tweet.html?id=1368289251234697216"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1368289251234697216-210');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1368289251234697216&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.twitch.tv/whitep4nth3r" rel="noopener noreferrer"&gt;Salma Alam-Naylor&lt;/a&gt;
&lt;/h3&gt;


&lt;div class="ltag__user ltag__user__id__492757"&gt;
    &lt;a href="/whitep4nth3r" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F492757%2F6440ce47-9fbc-4865-a283-8aab7ea11580.png" alt="whitep4nth3r image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/whitep4nth3r"&gt;Salma Alam-Naylor&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/whitep4nth3r"&gt;I write code for your entertainment.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  &lt;a href="https://www.twitch.tv/laylacodesit" rel="noopener noreferrer"&gt;Layla Porter&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1368841067806814208-166" src="https://platform.twitter.com/embed/Tweet.html?id=1368841067806814208"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1368841067806814208-166');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1368841067806814208&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  Last Year Me
&lt;/h2&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/lukeocodes" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F133562%2F7ca76112-5580-4245-8a48-b24bf6f4fb51.jpg" alt="lukeocodes"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/lukeocodes/i-m-luke-and-i-could-do-more-1haj" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;I'm Luke, and I Could Do More&lt;/h2&gt;
      &lt;h3&gt;@lukeocodes 🕹👨‍💻 ・ Mar 4 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#wecoded&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#womenintech&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>wecoded</category>
      <category>career</category>
      <category>womenintech</category>
    </item>
    <item>
      <title>Server-Side Analytics with Jamstack Sites</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Thu, 26 Nov 2020 18:03:20 +0000</pubDate>
      <link>https://dev.to/lukeocodes/server-side-analytics-with-jamstack-sites-4bg6</link>
      <guid>https://dev.to/lukeocodes/server-side-analytics-with-jamstack-sites-4bg6</guid>
      <description>&lt;p&gt;Jamstack sites don't have a backend. That makes their ability to gather analytics particularly vulnerable to blockers. Let's fix that problem.&lt;/p&gt;

&lt;p&gt;This example includes a &lt;a href="https://www.netlify.com/products/functions" rel="noopener noreferrer"&gt;Netlify Function&lt;/a&gt; that will send our events off to Google Analytics, and a &lt;a href="https://docs.netlify.com/routing/redirects/" rel="noopener noreferrer"&gt;Netlify Redirect&lt;/a&gt; rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Trackers and tracking pixels are HTML code designed to capture user behaviour or visits when they visit a website or open an email. It is useful for tracking usage of your website, and sometimes conversions.&lt;/p&gt;

&lt;p&gt;The problem is, that some trackers are slow and often invasive. Ad-blockers were first dreamt up to stop ads and tracking pixels slowing down webpage performance, or to improve a user's experience, but have subsequently been expanded to improve privacy for users.&lt;/p&gt;

&lt;p&gt;A side-effect was, that a lot of site owners lost visibility of what worked and didn't work on their sites. Physical tracking characteristics can still be used to track certain metrics, e.g. adding an article's identifier on a sign-up link to see where the sign-up originated.&lt;/p&gt;

&lt;p&gt;This still doesn't help us accurately determine if our content is being viewed, a key requirement to determine conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Server-side analytics has become a popular way to track user activity. It doesn't have the scope of traditional analytics (it can't easily track on-page interactions), but it can capture important details, like unique page views.&lt;/p&gt;

&lt;p&gt;Hosting platforms such as &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, or Edge providers like Cloudflare and Fastly, offer Server-side analytics as part of their solutions. But, when using a provider for analytics, you're often restricted in how you can warehouse that information, limiting internal reporting.&lt;/p&gt;

&lt;p&gt;For that reason, some like to roll their own server-side analytics. For this, Google has some quick starts for some languages, and for others there are packages like &lt;a href="https://www.npmjs.com/package/universal-analytics" rel="noopener noreferrer"&gt;&lt;code&gt;universal-analytics&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here, we'll roll our own using &lt;code&gt;universal-analytics&lt;/code&gt; and a Netlify Function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Netlify Function
&lt;/h3&gt;

&lt;p&gt;Netlify Functions are basically AWS Lambda functions, without the AWS. The AWS developer experience leaves A LOT to be desired, and Netlify have turned user experience into a business model. Netlify Functions are no exception, allowing folks to write JavaScript or Go to a configured directory, and publish it in a few steps. The endpoint is derived by the file or folder name, and it can use the dependencies from the parent application, or be responsible for its own.&lt;/p&gt;

&lt;p&gt;A super simple function might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// functions/hello-world/index.js&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Only the server will see this!&lt;/span&gt;&lt;span class="dl"&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="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once deployed, you would be able to access it at a URL like this one: &lt;code&gt;https://your-app.netlify.app/.netlify/functions/hello-world&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But you can also continue to do things after you send a response back, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// functions/hello-world/index.js&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The server will still see this!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we could use this functionality to send off our analytics to Google.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: If you're adding dependencies to a function, you'll need to add the &lt;code&gt;@netlify/plugin-functions-install-core&lt;/code&gt; plugin to your &lt;code&gt;netlify.toml&lt;/code&gt; configuration. This plugin will ensure all the function's dependencies are installed when the function is deployed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We need to install &lt;code&gt;universal-analytics&lt;/code&gt; first, so make sure you're in your function's directory before you run the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;universal-analytics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure that you have a Google Analytics ID, and add that to your &lt;a href="https://docs.netlify.com/configure-builds/environment-variables/" rel="noopener noreferrer"&gt;Netlify Environment Variables&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, we can use it in our function.&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;// functions/hello-world/index.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ua&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;universal-analytics&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;visitor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ua&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;GOOGLE_ANALYTICS_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;queryStringParameters&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;

  &lt;span class="k"&gt;try&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;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pageview&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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// eslint-disable-line&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, from your browser, you can send off data from the URL straight to Google: &lt;code&gt;https://your-app.netlify.app/.netlify/functions/hello-world?dp=/my-custom-page&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Data you can send off includes—but isn't limited to—these parameters:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// path e.g. /my-custom-page&lt;/span&gt;
  &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// title of the page&lt;/span&gt;
  &lt;span class="nx"&gt;dh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// hostname e.g. https://netlify.com&lt;/span&gt;
  &lt;span class="nx"&gt;dr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// referrer e.g. https://netlify.com/as-a-referrer or /a-link&lt;/span&gt;
  &lt;span class="nx"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// user agent e.g. very obscure string meaning "chrome on mac"&lt;/span&gt;
  &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// utm_source&lt;/span&gt;
  &lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// utm_medium&lt;/span&gt;
  &lt;span class="nx"&gt;cn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// utm_campaign&lt;/span&gt;
  &lt;span class="nx"&gt;ck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// utm_term&lt;/span&gt;
  &lt;span class="nx"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// utm_content&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a &lt;a href="https://github.com/peaksandpies/universal-analytics/blob/HEAD/AcceptableParams.md" rel="noopener noreferrer"&gt;full list of acceptable parameters&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the "Image"
&lt;/h3&gt;

&lt;p&gt;Calling this script alone, with something like a router middleware or AJAX request, might be enough in a lot of instances for decent reporting, but it could still be recognised as an XHR request by a browser or browser ad-blocker, and blocked.&lt;/p&gt;

&lt;p&gt;A (typically over-engineered) solution that I decided to use was similar to a tracking pixel method. But, because we return a visible structural image, ad-blockers have completely ignored it so-far.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4yrybqv7g2ultpicyiar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4yrybqv7g2ultpicyiar.png" title="Screenshot of AdBlock Plus" alt="Screenshot of AdBlock Plus ignoring the tracker on Vonage Learn" width="338" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're going to return an SVG image from the Netlify Function and place it on a page using an image tag.&lt;/p&gt;

&lt;p&gt;Let's use this image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn4zjx6l0i4pyrzlepfg7.png" class="article-body-image-wrapper"&gt;&lt;img alt="An SVG file of a very smiley Emoji" title="An SVG Emoji" src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn4zjx6l0i4pyrzlepfg7.png" width="136" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The source for this image can be found here:&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;svg id="svg1923" width="733" xmlns="http://www.w3.org/2000/svg" height="733"&amp;gt;
&amp;lt;circle cy="366.5" cx="366.5" r="366.5"/&amp;gt;
&amp;lt;circle cy="366.5" cx="366.5" r="336.5" fill="#fede58"/&amp;gt;
&amp;lt;path d="m325 665c-121-21-194-115-212-233v-8l-25-1-1-18h481c6 13 10 27 13 41 13 94-38 146-114 193-45 23-93 29-142 26z"/&amp;gt;
&amp;lt;path d="m372 647c52-6 98-28 138-62 28-25 46-56 51-87 4-20 1-57-5-70l-423-1c-2 56 39 118 74 157 31 34 72 54 116 63 11 2 38 2 49 0z" fill="#871945"/&amp;gt;
&amp;lt;path d="m76 342c-13-26-13-57-9-85 6-27 18-52 35-68 21-20 50-23 77-18 15 4 28 12 39 23 18 17 30 40 36 67 4 20 4 41 0 60l-6 21z"/&amp;gt;
&amp;lt;path d="m234 323c5-6 6-40 2-58-3-16-4-16-10-10-14 14-38 14-52 0-15-18-12-41 6-55 3-3 5-5 5-6-1-4-22-8-34-7-42 4-57.6 40-66.2 77-3 17-1 53 4 59h145.2z" fill="#fff"/&amp;gt;
&amp;lt;path d="m378 343c-2-3-6-20-7-29-5-28-1-57 11-83 15-30 41-52 72-60 29-7 57 0 82 15 26 17 45 49 50 82 2 12 2 33 0 45-1 10-5 26-8 30z"/&amp;gt;
&amp;lt;path d="m565 324c4-5 5-34 4-50-2-14-6-24-8-24-1 0-3 2-6 5-17 17-47 13-58-9-7-16-4-31 8-43 4-4 7-8 7-9 0 0-4-2-8-3-51-17-105 20-115 80-3 15 0 43 3 53z" fill="#fff"/&amp;gt;
&amp;lt;path d="m504 590s-46 40-105 53c-66 15-114-7-114-7s14-76 93-95c76-18 126 49 126 49z" fill="#f9bedd"/&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, to return the image from our function:&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;// functions/hello-world/index.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ua&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;universal-analytics&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;visitor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ua&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;GOOGLE_ANALYTICS_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&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;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/svg+xml&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;svg id="svg1923" width="733" xmlns="http://www.w3.org/2000/svg" height="733"&amp;gt;
&amp;lt;circle cy="366.5" cx="366.5" r="366.5"/&amp;gt;
&amp;lt;circle cy="366.5" cx="366.5" r="336.5" fill="#fede58"/&amp;gt;
&amp;lt;path d="m325 665c-121-21-194-115-212-233v-8l-25-1-1-18h481c6 13 10 27 13 41 13 94-38 146-114 193-45 23-93 29-142 26z"/&amp;gt;
&amp;lt;path d="m372 647c52-6 98-28 138-62 28-25 46-56 51-87 4-20 1-57-5-70l-423-1c-2 56 39 118 74 157 31 34 72 54 116 63 11 2 38 2 49 0z" fill="#871945"/&amp;gt;
&amp;lt;path d="m76 342c-13-26-13-57-9-85 6-27 18-52 35-68 21-20 50-23 77-18 15 4 28 12 39 23 18 17 30 40 36 67 4 20 4 41 0 60l-6 21z"/&amp;gt;
&amp;lt;path d="m234 323c5-6 6-40 2-58-3-16-4-16-10-10-14 14-38 14-52 0-15-18-12-41 6-55 3-3 5-5 5-6-1-4-22-8-34-7-42 4-57.6 40-66.2 77-3 17-1 53 4 59h145.2z" fill="#fff"/&amp;gt;
&amp;lt;path d="m378 343c-2-3-6-20-7-29-5-28-1-57 11-83 15-30 41-52 72-60 29-7 57 0 82 15 26 17 45 49 50 82 2 12 2 33 0 45-1 10-5 26-8 30z"/&amp;gt;
&amp;lt;path d="m565 324c4-5 5-34 4-50-2-14-6-24-8-24-1 0-3 2-6 5-17 17-47 13-58-9-7-16-4-31 8-43 4-4 7-8 7-9 0 0-4-2-8-3-51-17-105 20-115 80-3 15 0 43 3 53z" fill="#fff"/&amp;gt;
&amp;lt;path d="m504 590s-46 40-105 53c-66 15-114-7-114-7s14-76 93-95c76-18 126 49 126 49z" fill="#f9bedd"/&amp;gt;
&amp;lt;/svg&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;queryStringParameters&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;

  &lt;span class="k"&gt;try&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;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pageview&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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// eslint-disable-line&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;Add it to your site using the URL we used before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; 
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"An SVG file of a very smiley Emoji"&lt;/span&gt;
  &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"An SVG Emoji"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"128"&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/.netlify/functions/hello-world?dp=/my-custom-page"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With any luck, it'll look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwzrgh73tuu4pw0kacp76.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwzrgh73tuu4pw0kacp76.png" title="An image of a smiley" alt="Screenshot of the smiley included in page" width="731" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Redirect Rule
&lt;/h3&gt;

&lt;p&gt;So, the most &lt;del&gt;devious&lt;/del&gt; inventive part of my &lt;del&gt;evil plan&lt;/del&gt; idea might be this next bit. I was slightly paranoid that an ad-blocker might sense a non-image URL with query string parameters as little suspect as an image source, and block it anyway.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://docs.netlify.com/routing/redirects/" rel="noopener noreferrer"&gt;Netlify Redirects&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;netlify.toml&lt;/code&gt; in the root of your project, you can proxy one path with another.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[redirects]]&lt;/span&gt;
  &lt;span class="py"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/images/smiley-face.svg"&lt;/span&gt;
  &lt;span class="py"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/.netlify/functions/hello-world"&lt;/span&gt;
  &lt;span class="py"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
  &lt;span class="py"&gt;force&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can use an image path to include your smiley face.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; 
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"An SVG file of a very smiley Emoji"&lt;/span&gt;
  &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"An SVG Emoji"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"128"&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/images/smiley-face.svg?dp=/my-custom-page"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can encode the query string however you like, just remember to decode it inside the function.&lt;/p&gt;

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

&lt;p&gt;Analytics is a superpower for marketing and content creators. It allows us to better serve the community, by tuning our goals based on the data we can collect.&lt;/p&gt;

&lt;p&gt;There are often privacy and speed concerns around trackers. But as long as you're acting in good faith, as I believe we are, analytics benefits viewers as much as anyone else.&lt;/p&gt;

&lt;p&gt;This is a nice little way to achieve server-side analytics (which blockers can't block), when you don't have a server-side at your disposal.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>nuxt</category>
      <category>netlify</category>
      <category>jamstack</category>
    </item>
    <item>
      <title>Migration From WordPress to Jamstack (Nuxt edition)</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Mon, 23 Nov 2020 10:34:17 +0000</pubDate>
      <link>https://dev.to/vonagedev/migration-from-wordpress-to-jamstack-nuxt-edition-59ak</link>
      <guid>https://dev.to/vonagedev/migration-from-wordpress-to-jamstack-nuxt-edition-59ak</guid>
      <description>&lt;h1&gt;
  
  
  The Great Migration: Migrating WordPress to Jamstack
&lt;/h1&gt;

&lt;p&gt;If you do some development, editing, or writing on the internet, you've probably heard of WordPress. To say it's prolific is an understatement.&lt;/p&gt;

&lt;p&gt;Every time we talk about the market share of different frameworks, someone magics a new number out of the air for WordPress, a great point made by &lt;a href="https://dev.to/sarah_edo"&gt;Sarah Drasner&lt;/a&gt; as she wrote about when &lt;a href="https://www.smashingmagazine.com/2020/01/migration-from-wordpress-to-jamstack/" rel="noopener noreferrer"&gt;Smashing Magazine moved from WordPress to Preact/Hugo&lt;/a&gt; at the beginning of this year.&lt;/p&gt;

&lt;p&gt;I've been quite public about my issues with WordPress–security/speed/bloat/UX. Not to take anything away from WordPress developers, or the folks who are maintaining it, but for an organization like ours—with engineers, writers, and user experience professionals available to pitch in—living with a platform that is widely accepted as being clunky and heavy for the benefit of a good backend always felt a bit counter-intuitive.&lt;/p&gt;

&lt;p&gt;So, similarly to Sarah's post, I'm going to explore the whats/whys/wheres of this journey, since that meeting we had in Miami, early in 2020, before the world seemed to go to, well, COVID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;We were going through a rebrand, and the timing for us was perfect. Why invest in an agency to rebrand our WordPress site when we could produce a new site based on our brand from the ground up?&lt;/p&gt;

&lt;p&gt;We also had our content creation process split across three platforms. Our content was edited and reviewed as Markdown, moved to WordPress, and tracked on JIRA.&lt;/p&gt;

&lt;p&gt;As I mentioned above, we were well aware of the general concerns with the speed and security of WordPress.&lt;/p&gt;

&lt;p&gt;And on top of that, this WordPress site represented a piece of our infrastructure unknown to almost all our ops team. Vonage continues to work through consolidating the infrastructure of the API businesses it has acquired in recent years, and our Wordpress platform was an unnecessary remnant of that legacy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability
&lt;/h2&gt;

&lt;p&gt;Our Developer Education team sits inside Developer Relations, itself inside the Product organization. So we're not focused full-time engineers, and we don't own large amounts of infrastructure.&lt;/p&gt;

&lt;p&gt;Netlify allows us to fire-and-forget our content. We can get past the complexity, maintenance, security, and reliability concerns that WordPress brings with it. With Netlify, as long as our site can build, it can deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow
&lt;/h2&gt;

&lt;p&gt;As I mentioned already, we had a workflow split across three platforms. It could be frustrating and inaccessible, especially for external writers who didn't have access to our content repository, like those in our &lt;a href="https://developer.nexmo.com/spotlight" rel="noopener noreferrer"&gt;Spotlight programme&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the goals of this project was to find a way to simplify our workflow, hopefully impacting us as little as possible. Netlify CMS allowed us to do this. &lt;/p&gt;

&lt;p&gt;The editorial workflow Netlify CMS provided reflected our existing JIRA workflow quite closely, giving me hopes of automation (or, another opportunity to log into JIRA less). At the same time, the git-based storage of Netlify CMS also reflected our existing review process.&lt;/p&gt;

&lt;p&gt;Using Netlify CMS allowed for a significant amount of the process to be consolidated.&lt;/p&gt;

&lt;p&gt;Migrating the content from WordPress ended up being the most significant hurdle we'd face. We had the WP REST API available, so off I went making API call after API call to try and identify the best way to extract our content from WordPress. We edited content in WordPress as Markdown, so it must store it as such? I was getting excited to think that I would could some API calls to retrieve our Markdown and save it as Markdown files.&lt;/p&gt;

&lt;p&gt;But was it stored as Markdown? Due to the nature of unmaintained community-driven plugins, nothing ended up being that straightforward.&lt;/p&gt;

&lt;p&gt;Our WordPress stored posts rendered as HTML. Crayon, the old and abandoned syntax highlighter plugin, seemed to keep code in tables, with columns for line numbers and rows per lines of code. The last version of Crayon before deprecation cited moving to store code in &lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/code&gt; tags much like other syntax highlighters. The goal of the last update was to make moving from it more manageable, as it would be compatible with converters or even other highlighters. But sadly, the plugin was so old, and the site so severely unmaintained we were facing an unrealistic obstacle updating everything to get the content out.&lt;/p&gt;

&lt;p&gt;The incredible irony of Crayon is that the maintainer had also had enough of WordPress and decided to move his site and focus to Jekyll, a Jamstack platform.&lt;/p&gt;

&lt;p&gt;We decided to review all our content manually. We don't have the thousands of articles of Smashing Magazine, but we have over 500 pieces of content. I mentioned rebranding earlier. The decision allowed us to revisit every piece of content to update the branding, update SDK versions, request new artwork, and bring them into 2020 (the poor things).&lt;/p&gt;

&lt;p&gt;But, how do you plan to produce new content AND review all content in a matter of weeks? Well, you don't. The plan would be to do the content review over a few months.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Plan
&lt;/h3&gt;

&lt;p&gt;Using rewrite rules, we would stop folks from being able to access the old site. They would be redirected to the same post on the &lt;a href="https://learn.vonage.com" rel="noopener noreferrer"&gt;new domain&lt;/a&gt;, where metadata would be imported as markdown files.&lt;/p&gt;

&lt;p&gt;The old site would be moved to a new "legacy" domain, with a link to it in each post we import.&lt;/p&gt;

&lt;p&gt;The new site would then provide a nice note to the effect of "We're still migrating this content", with a countdown to redirect them to the legacy link.&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%2Fi%2F6mzkrye0dn45v26fv14a.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%2Fi%2F6mzkrye0dn45v26fv14a.png" alt="Screenshot showing a message that the content hasn't been migrated yet and that the reader will be redirected to the old post" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we migrate content, we edit the markdown file we already imported, removing the legacy link and adding the migrated content, slotting the content in the middle of the user experience. The goal is to limit the impact on the user and reduce the strain on the team to migrate all our content quickly.&lt;/p&gt;

&lt;p&gt;To limit the impact further, we prioritised our most read and most recent content for migration, migrating most of them before we went live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework Choices
&lt;/h2&gt;

&lt;p&gt;I'd had some experience working with Jekyll and a similar workflow in the past. Jekyll, configured correctly, is blisteringly fast to render. I'd guess it's still right at the top for build speed when compared to other Jamstack platforms. It felt right to start there, with something I knew worked.&lt;/p&gt;

&lt;p&gt;I'd also been experimenting with Nuxt.js, because Vue.js is terrific and I'm a massive fan of Jamstack in general. Combing my two favourite things (Vumstack? Jamue?), I found Nuxt.js! Vonage also had a design system named Volta, based on Bootstrap, which applied all our branding guidelines, and was available as a Vue.js library.&lt;/p&gt;

&lt;p&gt;So I built two proof of concepts, one in Jekyll and one in Nuxt.js. Despite liquid templates being much easier to work with generally, I found myself prototyping Nuxt.js far more quickly due to Volta. With a frontend that already looked great with our branding and server-side rendering to make the site lightning quick, we were very excited about this Nuxt.js prototype. After a few weeks of tweaking and applying feedback, we had something close to what we have today.&lt;/p&gt;

&lt;p&gt;Nuxt.js was the way to go!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Two weeks after our proof-of-concept was finished, Volta was deprecated by the design team! We replaced it using TailwindCSS, which allowed us to achieve design parity with Volta, but with more predictable breakpoints and a larger number of utilities for responsive sites.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;The result for us has been transformative. We're going to be able to deliver more content types, more quickly and more reliably. We now have a platform that supports all our immediate goals for 2021 and the future. It also looks AMAZING, If I do say so myself.&lt;/p&gt;

&lt;p&gt;Migration continues, but the go-live day had no hiccups. We smoothly transitioned folks to the new platform, with redirects in place to legacy if necessary.&lt;/p&gt;

&lt;p&gt;Thanks to server-side analytics, we're seeing more accurate tracking than before, and we've got access to much more granular data to inform our writing goals for the future.&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%2Fi%2Fbdqr8yfizqhsa95sgf2y.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%2Fi%2Fbdqr8yfizqhsa95sgf2y.png" alt="Screenshot of the new learn.vonage.com homepage" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Posts
&lt;/h2&gt;

&lt;p&gt;Here are some posts I've made related to our work towards the migration.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;div class="ltag__link__content"&gt;
    &lt;div class="missing"&gt;
      &lt;h2&gt;Article No Longer Available&lt;/h2&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="ltag__link"&gt;
  &lt;div class="ltag__link__content"&gt;
    &lt;div class="missing"&gt;
      &lt;h2&gt;Article No Longer Available&lt;/h2&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="ltag__link"&gt;
  &lt;div class="ltag__link__content"&gt;
    &lt;div class="missing"&gt;
      &lt;h2&gt;Article No Longer Available&lt;/h2&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="ltag__link"&gt;
  &lt;div class="ltag__link__content"&gt;
    &lt;div class="missing"&gt;
      &lt;h2&gt;Article No Longer Available&lt;/h2&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/lukeocodes" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F133562%2F7ca76112-5580-4245-8a48-b24bf6f4fb51.jpg" alt="lukeocodes"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/lukeocodes/using-tailwind-css-with-vue-js-b1b" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Using Tailwind CSS with Vue.js&lt;/h2&gt;
      &lt;h3&gt;@lukeocodes 🕹👨‍💻 ・ Mar 19 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#vue&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#beginners&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>netlify</category>
      <category>jamstack</category>
      <category>nuxt</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Get Your Twitch Stream Live on Your Website</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Mon, 16 Nov 2020 15:29:55 +0000</pubDate>
      <link>https://dev.to/lukeocodes/netlify-function-for-twitch-stream-status-poh</link>
      <guid>https://dev.to/lukeocodes/netlify-function-for-twitch-stream-status-poh</guid>
      <description>&lt;h1&gt;
  
  
  Is My Twitch Stream Live?
&lt;/h1&gt;

&lt;p&gt;Ever wanted your Twitch live status on your own homepage? Well, we achieve that with a small Netlify function called from our site.&lt;/p&gt;

&lt;p&gt;For the purposes of this post, let's assume credentials are always stored in environment variables...&lt;/p&gt;

&lt;p&gt;Diving Right In™️&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Twitch Credentials
&lt;/h2&gt;

&lt;p&gt;Log into your Twitch account, head over &lt;a href="https://dev.twitch.tv/console/apps" rel="noopener noreferrer"&gt;your Developer Console applications&lt;/a&gt; and &lt;strong&gt;Register a New Application with Twitch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Give the application a Name, OAuth Redirect URL, and Category.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Important?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;My Twitch Stream Live?&lt;/td&gt;
&lt;td&gt;Not really&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OAuth Redirect URL&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Not really&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Category&lt;/td&gt;
&lt;td&gt;Website Integration&lt;/td&gt;
&lt;td&gt;Not really&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Yes, I mean it, none of these are really that important. The client credentials grant required for server-to-server authentication doesn't require a redirect URL, because it can validate the client ID and secret on the one leg.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd7ztlfdemp3h9tter6b2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd7ztlfdemp3h9tter6b2.png" alt="Diagram of the Client Credentials Grant returning a token in a single request" width="763" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you click &lt;strong&gt;Create&lt;/strong&gt;, you'll be able to click on &lt;strong&gt;Manage&lt;/strong&gt; for your new application. Here you'll find the Client ID, and generate a &lt;strong&gt;New Secret&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Add the Client ID and Client Secret to environment variables.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TWITCH_CLIENT_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TWITCH_CLIENT_SECRET&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create your Netlify Function
&lt;/h2&gt;

&lt;p&gt;In your functions directory (we'll call ours &lt;code&gt;functions/&lt;/code&gt;), create a new directory called &lt;code&gt;live-on-twitch&lt;/code&gt; and change into it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;functions/
&lt;span class="nb"&gt;mkdir &lt;/span&gt;live-on-twitch
&lt;span class="nb"&gt;cd &lt;/span&gt;live-on-twitch/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialise your npm application.&lt;/p&gt;

&lt;p&gt;Now, add the basic structure of your function. It's not a very semantic endpoint, returning &lt;code&gt;status: online&lt;/code&gt; or &lt;code&gt;status: offline&lt;/code&gt;, and always a 200. 😇&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;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&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;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;offline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launching &lt;a href="http://localhost:55359/.netlify/functions/live-on-twitch" rel="noopener noreferrer"&gt;http://localhost:55359/.netlify/functions/live-on-twitch&lt;/a&gt;, and making a request will return JSON. This URL is generated by &lt;code&gt;netlify dev&lt;/code&gt; and may differ from what you see.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"offline"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Request an App Access Tokens
&lt;/h2&gt;

&lt;p&gt;Now, install &lt;code&gt;axios&lt;/code&gt;, to make the requests with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;axios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the request options and then turn them into parameters for a post request to the Twitch OAuth service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;querystring&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;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;client_id&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;TWITCH_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_secret&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;TWITCH_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_credentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://id.twitch.tv/oauth2/token?&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="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="c1"&gt;// callback&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Is Your Twitch Stream Live?
&lt;/h2&gt;

&lt;p&gt;With the &lt;code&gt;access_token&lt;/code&gt; returned from Twitch, now you can request the status of your stream.&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;// requires&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// get access_token&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;streamUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vonagedevs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="kd"&gt;const&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="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;streams&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://api.twitch.tv/helix/streams?user_login=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;streamUser&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="na"&gt;headers&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;Client-ID&lt;/span&gt;&lt;span class="dl"&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;TWITCH_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &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="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// callback&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because you look for a single &lt;code&gt;user_login&lt;/code&gt; from the streams endpoint, we'll assume a &lt;code&gt;stream.length&lt;/code&gt; is &lt;code&gt;online&lt;/code&gt;, as it will be zero if you're offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Code
&lt;/h2&gt;

&lt;p&gt;Here is the function in full.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;querystring&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;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;client_id&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;TWITCH_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_secret&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;TWITCH_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_credentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://id.twitch.tv/oauth2/token?&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="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;streamUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vonagedevs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="kd"&gt;const&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="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;streams&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://api.twitch.tv/helix/streams?user_login=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;streamUser&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="na"&gt;headers&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;Client-ID&lt;/span&gt;&lt;span class="dl"&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;TWITCH_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &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="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&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;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;streams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;online&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;offline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Did We Use It For?
&lt;/h2&gt;

&lt;p&gt;If we're live on Twitch, we're going to enhance the landing page of our blog with the stream!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbcmerxbtnn2nhxxmwo9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbcmerxbtnn2nhxxmwo9s.png" alt="Our stream is live on our site when we're online" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>netlify</category>
      <category>lambda</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The Perfect Breadcrumbs (in Nuxt)</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Tue, 10 Nov 2020 09:46:52 +0000</pubDate>
      <link>https://dev.to/lukeocodes/breadcrumbs-in-nuxt-5f2m</link>
      <guid>https://dev.to/lukeocodes/breadcrumbs-in-nuxt-5f2m</guid>
      <description>&lt;p&gt;Breadcrumbs can be a bit of a pain. But there are lots of reasons you might want to make them.&lt;/p&gt;

&lt;p&gt;TL;DR: I made a self-contained component that builds semantic breadcrumbs based on the path to the file using the router. It matches the paths against the router before outputting links.&lt;/p&gt;

&lt;p&gt;The gist is at the end of the post. &lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Breadcrumbs and Do They Really Deserve This Much Attention?
&lt;/h2&gt;

&lt;p&gt;Breadcrumbs most commonly appear at the top of a page as text links denoting the path to the post (or back to the index). Breadcrumbs are an important navigation mechanism.&lt;/p&gt;

&lt;p&gt;Combined with structured data or semantic markup like RDFa, they also act as an important SEO tool, for sites such as Google to understand the structure of your site.&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%2F476kky930fh3xgb871hx.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%2F476kky930fh3xgb871hx.png" alt="Showing breadcrumbs on Vonage.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When Google finds the data it needs, it can display the site structure in results.&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%2Fhbuyt5t5cgvpg5heybz7.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%2Fhbuyt5t5cgvpg5heybz7.png" alt="Showing Vonage.com breadcrumbs in a Google result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Make This?
&lt;/h2&gt;

&lt;p&gt;Most of the examples I found online take an array from the page you're placing the breadcrumbs. This works from the &lt;code&gt;/&lt;/code&gt; divided path but skips paths that the router cannot match.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does It Work?
&lt;/h2&gt;

&lt;p&gt;I'll focus on the JS rather than the JSX. You'll likely make better markup for it than I would.&lt;/p&gt;

&lt;p&gt;Starting with an empty output.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crumbs&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;Now, we'll get the current full path.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="c1"&gt;// url:     /blog/2020/11/20/my-post-url&lt;/span&gt;
      &lt;span class="c1"&gt;// outputs: ['blog','2020','11','20','my-post-url']&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crumbs&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;Next, recompile the URL bit by bit.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;

      &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;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;param&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="c1"&gt;// outputs: /blog&lt;/span&gt;
      &lt;span class="c1"&gt;//          /blog/2020&lt;/span&gt;
      &lt;span class="c1"&gt;//          /blog/2020/11&lt;/span&gt;
      &lt;span class="c1"&gt;//          /blog/2020/11/20&lt;/span&gt;
      &lt;span class="c1"&gt;//          /blog/2020/11/20/my-post-url&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crumbs&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;Now, match each route on the router.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;

      &lt;span class="c1"&gt;// test path&lt;/span&gt;
      &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fake&lt;/span&gt;&lt;span class="dl"&gt;'&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="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;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;param&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`yep:  &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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`nope: &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="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="c1"&gt;// outputs: yep:  /blog&lt;/span&gt;
      &lt;span class="c1"&gt;//          yep:  /blog/2020&lt;/span&gt;
      &lt;span class="c1"&gt;//          yep:  /blog/2020/11&lt;/span&gt;
      &lt;span class="c1"&gt;//          yep:  /blog/2020/11/20&lt;/span&gt;
      &lt;span class="c1"&gt;//          yep:  /blog/2020/11/20/my-post-url&lt;/span&gt;
      &lt;span class="c1"&gt;//          nope: /blog/2020/11/20/my-post-url/fake&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crumbs&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;Finally, capture only matches.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;

      &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;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;param&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crumbs&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 mine, I turn the param into a title using &lt;code&gt;ap-style-title-case&lt;/code&gt;. I have a prop that I let folks override the autogenerated page title for blog posts where the slug might not perfectly turn back into a title.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;titleCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ap-style-title-case&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="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&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="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&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="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;

      &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;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;param&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;titleCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-/g&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crumbs&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;h2&gt;
  
  
  The Full Code
&lt;/h2&gt;

&lt;p&gt;Check out the gist for the whole component!&lt;/p&gt;


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



</description>
      <category>nuxt</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Speed up Nuxt Builds on Netlify</title>
      <dc:creator>@lukeocodes 🕹👨‍💻</dc:creator>
      <pubDate>Sun, 25 Oct 2020 12:32:09 +0000</pubDate>
      <link>https://dev.to/lukeocodes/make-nuxt-js-go-brrrrrrrr-30-minute-to-1-minute-builds-on-netlify-f6e</link>
      <guid>https://dev.to/lukeocodes/make-nuxt-js-go-brrrrrrrr-30-minute-to-1-minute-builds-on-netlify-f6e</guid>
      <description>&lt;p&gt;If you're not careful, your build times for &lt;a href="https://nuxtjs.org/"&gt;Nuxt&lt;/a&gt; can spiral out of control. After enabling &lt;a href="https://i18n.nuxtjs.org/"&gt;Nuxt i18n&lt;/a&gt;, we reached 27-minute production deploys.&lt;/p&gt;

&lt;p&gt;Once we had added a load of nice-to-have Netlify plugins, our deploy times went up again. Before I knew it, our deploys on Netlify were failing, timing out at 30 minutes.&lt;/p&gt;

&lt;p&gt;This post addresses build time problems for sites using version &lt;code&gt;2.14&lt;/code&gt; of &lt;a href="https://nuxtjs.org/"&gt;Nuxt&lt;/a&gt;. &lt;code&gt;2.14&lt;/code&gt; introduces &lt;a href="https://nuxtjs.org/blog/going-full-static/#crawler-integrated"&gt;full-static&lt;/a&gt; builds and includes all the lovely new crawler changes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: I will refer to the time it takes for Nuxt to run &lt;code&gt;generate&lt;/code&gt; as build times. Deploy times are the time it takes Netlify to run &lt;code&gt;generate&lt;/code&gt; and then publish it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this post, I'll be using the globally recognised "turd" scale for measuring satisfaction–💩💩💩 to 😍🎉🔥.&lt;/p&gt;

&lt;p&gt;💩💩💩 30-minute deploys (timeout)&lt;/p&gt;

&lt;h2&gt;
  
  
  Unnecessary Content
&lt;/h2&gt;

&lt;p&gt;Unnecessary content was an issue for us. Having imported nearly 600 articles from our legacy WordPress site, we were building pages for every category, tag, and author too. This lead to some 17500 physical pages being rendered by the Nuxt full static build. After reviewing the metadata on our posts, I managed to reduce our build to just over 3000 pages...&lt;/p&gt;

&lt;p&gt;Our Netlify build jumped from 30-minute timeouts to 15 minute deploys.&lt;/p&gt;

&lt;p&gt;👎🏻👎🏻👎🏻 15-minute deploys&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hD-9xFBw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/wy7lz0u9v9mgchu7ka3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hD-9xFBw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/wy7lz0u9v9mgchu7ka3p.png" alt="Screenshot of 15-minute deploy in Netlify" width="800" height="76"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Skip Optional Dependencies
&lt;/h2&gt;

&lt;p&gt;While not Nuxt specific, only installing the dependencies you need can speed up the Netlify install before the build even starts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.npmjs.com/cli/install"&gt;The &lt;code&gt;--no-optional&lt;/code&gt; argument&lt;/a&gt; will prevent optional dependencies from being installed by &lt;code&gt;npm&lt;/code&gt;. &lt;a href="https://classic.yarnpkg.com/en/docs/cli/install/#toc-yarn-install-ignore-optional"&gt;There is a yarn equivalent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can add this to Netlify in the config, or add it to the &lt;a href="https://docs.netlify.com/configure-builds/environment-variables/"&gt;environment variables&lt;/a&gt; on the dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# netlify.toml&lt;/span&gt;

&lt;span class="nn"&gt;[build.environment]&lt;/span&gt; 
  &lt;span class="py"&gt;NPM_FLAGS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"--no-optional"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This did nothing for our deploy time, but it may help others. 🤪&lt;/p&gt;

&lt;p&gt;👎🏻👎🏻👎🏻 15-minute deploys &lt;/p&gt;
&lt;h2&gt;
  
  
  Code Minification
&lt;/h2&gt;

&lt;p&gt;Nuxt has strong &lt;a href="https://github.com/nuxt/nuxt.js/blob/5fa768373da1adfd8c76145b2ec95b7824af93b4/packages/config/src/config/build.js#L89-L101"&gt;default HTML minification settings&lt;/a&gt; used for post-processing builds.&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;// ...&lt;/span&gt;

  &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;minify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;collapseBooleanAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;decodeEntities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;minifyCSS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;minifyJS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;processConditionalComments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;removeEmptyAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;removeRedundantAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;trimCustomFragments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;useShortDoctype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Nuxt already minifies CSS and JS using WebPack plugins. So we can disable inline CSS and JS minification.&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;// ...&lt;/span&gt;

  &lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;minify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;minifyCSS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;minifyJS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When I read about this, it suggested we'd see a 10x improvement on the initial build. In reality, we saw a minute or two in reductions.&lt;/p&gt;

&lt;p&gt;👎🏻👎🏻👍🏻 13-minute deploys&lt;/p&gt;
&lt;h2&gt;
  
  
  Turn Off Logging
&lt;/h2&gt;

&lt;p&gt;Even locally, the verbose logging of several thousand lines in the terminal can slow EVERYTHING down. Most of the logging is formatted from Nuxt, to.&lt;/p&gt;

&lt;p&gt;Disable logging anything but errors with the &lt;code&gt;CI&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;You can add this to Netlify in the config, or add it to the &lt;a href="https://docs.netlify.com/configure-builds/environment-variables/"&gt;environment variables&lt;/a&gt; on the dashboard.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# netlify.toml&lt;/span&gt;

&lt;span class="nn"&gt;[build.environment]&lt;/span&gt;
  &lt;span class="py"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This made a surprising difference, slicing a fair chunk off our build time.&lt;/p&gt;

&lt;p&gt;👎🏻👍🏻👍🏻 8-minute deploys&lt;/p&gt;
&lt;h2&gt;
  
  
  Post Processing
&lt;/h2&gt;

&lt;p&gt;If you've played with Netlify deployment configurations, you'll know there is a wealth of options now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CmTLS9np--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/3kavvlk488vuzibl1wad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CmTLS9np--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/3kavvlk488vuzibl1wad.png" alt="All the Netlify deploy post-processing options" width="800" height="1085"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had lots of options ticked, and we've already established that Nuxt does most of it already.&lt;/p&gt;

&lt;p&gt;Reasons to turn them &lt;strong&gt;&lt;em&gt;all&lt;/em&gt;&lt;/strong&gt; of—for us at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We already have a plan to generate our images based on rules we've established for media creation already. We can do the optimisation steps and CDN uploads here in the future.&lt;/li&gt;
&lt;li&gt;Nuxt does minification of HTML, JS and CSS already.&lt;/li&gt;
&lt;li&gt;The Nuxt static build does-Pre-rendering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tick, tick, tick. All off.&lt;/p&gt;

&lt;p&gt;👍🏻👍🏻👍🏻 5-minute deploys&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TUvPCU1A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/iq4jnar33uladjhxz5ih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TUvPCU1A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/iq4jnar33uladjhxz5ih.png" alt="Screenshot of 5-minute deploy in Netlify" width="800" height="79"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Real Talk
&lt;/h2&gt;

&lt;p&gt;Getting our deploys much quicker &lt;a href="https://www.netlify.com/blog/2020/03/25/announcing-faster-deploys-for-large-sites/"&gt;without paying for enterprise Netlify&lt;/a&gt; is unrealistic. I have a ton of optimisation to do on my Vue components, but I don't expect to see &lt;em&gt;much&lt;/em&gt; more time saved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;It wasn't actually the production build I was so keen to reduce the time on&lt;/em&gt;&lt;/strong&gt;, it was the previews as we've gone FULL NETLIFY and adopted Netlify CMS for git-based content storage. So, every time we edit a post in Netlify CMS, it creates a pull-request for the edited file. At 30 minute builds, with a team of 8 people working on content, well you see where this is going—lots of waiting for builds. Less now, at least.&lt;/p&gt;
&lt;h2&gt;
  
  
  BUT WAIT, There's More 😮
&lt;/h2&gt;

&lt;p&gt;Is there a way to reduce our preview builds? That was the original motivation to reduce build times in the first place!&lt;/p&gt;

&lt;p&gt;After a brief Googlin', I came across &lt;a href="https://github.com/nuxt/nuxt.js/issues/6138"&gt;issue #6138 raised on the Nuxt project&lt;/a&gt;, on how to generate a single route.&lt;/p&gt;

&lt;p&gt;In the latest version of Nuxt, the solutions in the GitHub issue didn't actually work.&lt;/p&gt;
&lt;h5&gt;
  
  
  But, it did give me an idea.
&lt;/h5&gt;

&lt;p&gt;Since Nuxt &lt;code&gt;2.14&lt;/code&gt;, we've had the crawler to discover pages. And, if I want to provide additional routes, I can use the &lt;a href="https://nuxtjs.org/api/configuration-generate/#routes"&gt;&lt;code&gt;routes()&lt;/code&gt;&lt;/a&gt; property of the &lt;a href="https://nuxtjs.org/api/configuration-generate/#routes"&gt;generator&lt;/a&gt; config.&lt;/p&gt;

&lt;p&gt;So, I thought to myself, "can I turn off the crawler and provide it a single route, somehow"?&lt;/p&gt;
&lt;h5&gt;
  
  
  The answer was yes.
&lt;/h5&gt;

&lt;p&gt;Casually breaking my &lt;code&gt;nuxt.config.js&lt;/code&gt;...&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;crawler&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="nx"&gt;routes&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blog/a-test-blog-post-made-in-netlify-cms&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Doing this resulted in almost immediate build times, so once tested on Netlify I was down to about &lt;em&gt;1 minute deploys&lt;/em&gt;. It was just building the physical routes (everything in your &lt;code&gt;/pages&lt;/code&gt; directory) without crawling for any dynamic routes. Our physical routes make up less of our site than was worth worrying about.&lt;/p&gt;

&lt;p&gt;Could I make this context-driven based on the preview build? &lt;/p&gt;
&lt;h5&gt;
  
  
  Well, it hit me like a slap around the face.
&lt;/h5&gt;

&lt;p&gt;The slug for the new post created in Netlify CMS was part of the branch name.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cms/blog/a-test-blog-post-made-in-netlify-cms&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And, the branch name was available on our preview build as the environment variable, &lt;code&gt;HEAD&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# console.log(process.env.HEAD)
cms/blog/a-test-blog-post-made-in-netlify-cms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h5&gt;
  
  
  I'm getting closer.
&lt;/h5&gt;

&lt;p&gt;A quick explore of the other environment variables provided in preview builds, I noticed &lt;code&gt;PULL_REQUEST&lt;/code&gt; is an indicator whether the build is from a pull/merge request (&lt;code&gt;true&lt;/code&gt;) or not (&lt;code&gt;false&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;So here was the rough code I put together to make use of this.  Add a new function to the top of the &lt;code&gt;nuxt.config.js&lt;/code&gt; file.&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;// nuxt.config.js&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPreviewBuild&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="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;PULL_REQUEST&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;HEAD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cms/&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="c1"&gt;// ...&lt;/span&gt;

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

&lt;/div&gt;


&lt;p&gt;So this returns whether its a PR &lt;strong&gt;&lt;em&gt;and&lt;/em&gt;&lt;/strong&gt; if the branch name begins with &lt;code&gt;cms/&lt;/code&gt; (generated by Netlify CMS).&lt;/p&gt;

&lt;p&gt;How can we use this? I'm glad you asked. Edit the "generate" property in &lt;code&gt;nuxt.config.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.js&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;crawler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isPreviewBuild&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isPreviewBuild&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blog/a-test-blog-post-made-in-netlify-cms&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Tested? Works, still my ~1 minute deploy! Now, route from the branch name. Another new function to &lt;code&gt;nuxt.config.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.js&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previewRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;HEAD&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="k"&gt;return&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;type&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;slug&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="c1"&gt;// ...&lt;/span&gt;

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

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;type&lt;/code&gt; is super important because it means we can also preview &lt;code&gt;video&lt;/code&gt; and &lt;code&gt;author&lt;/code&gt; pages as well &lt;code&gt;blog&lt;/code&gt;. Really cool side effect. Added with one last edit to the &lt;code&gt;nuxt.config.js&lt;/code&gt; file.&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;// nuxt.config.js&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;crawler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isPreviewBuild&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isPreviewBuild&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;previewRoute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;😍🎉🔥 1-minute deploys&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sEAp4thX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/muecyflobomroqw6rlnv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sEAp4thX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/muecyflobomroqw6rlnv.png" alt="Screenshot of 1-minute deploy in Netlify" width="800" height="96"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Confession: I built most of the final code without testing it, I used the &lt;code&gt;BRANCH&lt;/code&gt; environment variable thinking it would be the branch name. It is not. So when it didn't work, I thought I'd gone down a big old rabbit hole building this. I thought I'd wasted hours. Nope, got the environment variable wrong. Read the docs, folks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's been almost 5 years since I was in a role that had me building code that would be relied upon in a production environment. I shouldn't be surprised (but I am) that the off-the-shelf configuration for Nuxt isn't optimised for production builds.&lt;/p&gt;

&lt;p&gt;I'm generally cautious of introducing environment-aware code into an application, but I have ignored my better judgement as this is environment-aware-configuration as code. You should be very careful about introducing code that fundamentally changes how an application runs or is built based on the environment it is running in.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Know your platform.&lt;/li&gt;
&lt;li&gt;Read the flipping manual.&lt;/li&gt;
&lt;li&gt;Do all the Googlin'.&lt;/li&gt;
&lt;li&gt;Take care with environment-aware code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See exactly how we're using this on our GitHub repository.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Nexmo"&gt;
        Nexmo
      &lt;/a&gt; / &lt;a href="https://github.com/Nexmo/deved-platform"&gt;
        deved-platform
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Nuxt.js for the new Vonage Developer Education site.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/nuxt/nuxt.js/issues/6138"&gt;issue #6138 raised on the Nuxt project&lt;/a&gt; 👏&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.voorhoede.nl/en/blog/10x-faster-nuxt-builds-on-netlify"&gt;10x Faster Nuxt Builds on Netlify&lt;/a&gt; ❤️&lt;/li&gt;
&lt;li&gt;Decent help from the &lt;a href="https://discord.nuxtjs.org/"&gt;Nuxt Discord&lt;/a&gt; community 🔥&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>netlify</category>
      <category>nuxt</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
