<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  xmlns:media="http://search.yahoo.com/mrss/"
  xmlns:webfeeds="http://webfeeds.org/rss/1.0">

  <channel>
    <title>TrackJS</title>
    <atom:link href="https://trackjs.com/feed.xml" rel="self" type="application/rss+xml" />
    <link rel="alternate">https://trackjs.com</link>
    <description>TrackJS tracks JavaScript errors from your frontend web applications with all the context you need to understand the impact and fix bugs fast.</description>
    <category>Frontend Error Tracking</category>
    <pubDate>Tue, 19 Nov 2024 21:38:15 +0000</pubDate>
    <lastBuildDate>Tue, 19 Nov 2024 21:38:15 +0000</lastBuildDate>
    <language>en-US</language>
    <managingEditor>hello@trackjs.com (TrackJS)</managingEditor>
    <webMaster>hello@trackjs.com (TrackJS)</webMaster>
    <copyright>2013-2024 TrackJS LLC. All rights reserved.</copyright>
    <sy:updatePeriod>daily</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <webfeeds:cover image="https://trackjs.com/assets/images/brand/share.png" />
    <webfeeds:icon>https://trackjs.com/assets/images/brand/trackjs-icon-512x512.png</webfeeds:icon>
    <webfeeds:logo>https://trackjs.com/assets/images/brand/logo.svg</webfeeds:logo>
    <webfeeds:accentColor>D03F40</webfeeds:accentColor>
    <webfeeds:related layout="card" target="browser"/>

    <image>
      <url>https://trackjs.com/assets/images/brand/trackjs-icon-512x512.png</url>
      <title>TrackJS</title>
      <link>https://trackjs.com</link>
      <width>512</width>
      <height>512</height>
    </image>

  
  
    
    
    
    <item>
		  <title>Common Errors in Next.js Caching</title>
		  <link>https://trackjs.com/blog/common-errors-in-nextjs-caching/</link>
      <guid isPermaLink="true">https://trackjs.com/blog/common-errors-in-nextjs-caching/</guid>
      <pubDate>Tue, 12 Nov 2024 00:00:00 +0000</pubDate>
		  <dc:creator><![CDATA[]]></dc:creator>
      <media:content url="https://trackjs.com/assets/images/blog/2024/common-errors-in-nextjs-caching/caching-in-nextjs.png" medium="image" type="image/png" height="1000" width="2000" />
      <enclosure url="https://trackjs.com/assets/images/blog/2024/common-errors-in-nextjs-caching/caching-in-nextjs.png" type="image/png" />
      <category><![CDATA[trackjs]]></category><category><![CDATA[error-monitoring]]></category><category><![CDATA[javascript]]></category><category><![CDATA[nextjs]]></category>
			<description><![CDATA[Next.js caching is complex and more than a bit magical. This complexity is a source silent errors in the form of stale and incorrect data.]]></description>
      <content:encoded><![CDATA[
        <div><img src="https://trackjs.com/assets/images/blog/2024/common-errors-in-nextjs-caching/caching-in-nextjs.png" alt="Common Errors in Next.js Caching" class="webfeedsFeaturedVisual" /></div>
        <p>Caching is one of the big draws for people using the Next.js framework. Its on-by-default, “just works” nature sets you up for high performance applications right out of the gate. However, improving <a href="https://requestmetrics.com">web performance</a> comes at the cost of a complex caching system. This complexity is a source silent errors in the form of stale and incorrect data. Next.js does its best to choose the right caching behavior for each page. Sometimes, it chooses wrong, resulting in unexpectedly stale pages or even pages that are slower than they should be.</p>

<p>Next.js caching is remarkable in that it caches so many things in so many places. Everything from fetch requests to rendered HTML and RSC payloads for hydrating an entire page are cached when possible. To add to the complexity, Next.js caches at many levels with build time, server and browser side caches.</p>

<p class="aside info"><strong>Note:</strong> All examples use the App Router and Next.js version 15 unless otherwise stated. However, much of this applies to version 14 as well.</p>

<h2 id="understanding-nextjs-caching">Understanding NextJS Caching</h2>

<h3 id="what-does-nextjs-cache">What Does Next.js Cache?</h3>
<p>Next.js does its best to cache as much as it can for as long as it can. There are three main things that are cached: <a href="#fetch-caching"><code>fetch()</code> response data</a>, <a href="#html-caching">rendered HTML</a> and <a href="#react-server-component-payloads-rsc">React Server Component(RSC) Payloads</a>.</p>

<h3 id="where-does-nextjs-cache-data">Where Does Next.js Cache Data?</h3>
<p>Next.js caches different kinds of data in all layers of the application using four main mechanisms. The <a href="#data-cache">Data Cache</a>, <a href="#request-memoization">Request Memoization</a> and the <a href="#full-route-cache">Full Route Cache</a> are all used to cache data on the server while the <a href="#router-cache">Router Cache</a> caches data in the browser. Not all caches are populated at run-time. The Full Route Cache even caches data during the build process by becoming part of the build artifact.</p>

<p>All these different data types, mechanisms and locations make for some confusion. You can generally determine where and what your data cache is caching your data by considering the kind of data you’re working with:</p>

<style>
	.test-table-class {
		border-spacing: 0;
		margin: 0 auto;

		td {
			padding: 4px 10px;
		}

		tbody {
			tr:nth-child(-n+2),
			tr:nth-child(n+4) {
				background-color: #faf7f5;//#f5f2f0;
			}
		}
	}
</style>

<table class="test-table-class">
  <thead>
    <tr>
      <th>Data Type</th>
      <th>Cache Mechanism</th>
      <th style="text-align: right">Where Stored</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>fetch() responses</td>
      <td>Data Cache</td>
      <td style="text-align: right">Build, Server</td>
    </tr>
    <tr>
      <td> </td>
      <td>Request Memoization</td>
      <td style="text-align: right">Server</td>
    </tr>
    <tr>
      <td>HTML</td>
      <td>Full Route Cache</td>
      <td style="text-align: right">Build, Server</td>
    </tr>
    <tr>
      <td>RSC Payloads</td>
      <td>Full Route Cache</td>
      <td style="text-align: right">Build, Server</td>
    </tr>
    <tr>
      <td> </td>
      <td>Router Cache</td>
      <td style="text-align: right">Client</td>
    </tr>
  </tbody>
</table>

<h3 id="how-does-nextjs-handle-stale-cache">How Does Next.js Handle Stale Cache?</h3>
<p>Next.js uses a “fetch for next time” strategy when handling stale data. This results in the user seeing some stale information. The cache is only checked for stale data when a request is made for that data. When stale data is discovered, it is returned as-is while fresh data is fetched in the background. This keeps pages fast, but causes expired data to display to the user at least once before it is reloaded.</p>

<h3 id="fetch-caching"><code>fetch()</code> Caching</h3>
<p>If there’s one thing Next.js loves caching, it’s data from API calls. Generally speaking, requests to external API’s are made using javascript’s built-in <a href="/javascript-errors/failed-to-fetch/">Fetch API</a>. Next.js wraps native <code>fetch()</code> to automatically store calls and return them from the cache.</p>

<p>Requests are cached at run-time on the server and also at build time when rendering static pages. Fetch caching is performed by two separate mechanisms within Next.js with the difference being how long the cache is valid.</p>

<p class="aside wide info"><strong>Don’t use fetch?</strong> Some things like SQL and other data sources have their own client libraries which don’t use <code>fetch()</code>. Next.js provides a <a href="#cache-date-when-not-using-fetch"><code>cache()</code></a> function for these situations. <a href="#cache-date-when-not-using-fetch"><code>cache()</code></a> allows any expensive or slow call to be cached while still benefiting from Next.js’s builtin caching behaviors.</p>

<p>Two mechanisms are used to cache fetch responses depending on whether the data is for same-request caching a longer term caching:</p>

<h4 id="same-request-caching">Same Request Caching</h4>
<p>Duplicate requests made within a single page load are cached by Next.js Request Memoization. This sounds fancy, but it’s just a per-request cache for <code>fetch()</code> and <code>cache()</code> calls. It lets you make requests in different components without worrying about duplicated requests. When Next.js sees that multiple requests share the same destination and arguments, only one request will be sent to the API. The other calls will be returned directly from the Request Memoization cache. You no longer need to pass a single response data from parent to child components to improve performance! The data in the cache will not be re-used once the request lifecycle has ended.</p>

<h4 id="cross-request-caching">Cross Request Caching</h4>
<p>When data should be used in multple requests, Next.js stores it in the Data Cache. The Data Cache saves <code>fetch</code> response data just like Request Memoization, but reuses responses in more than one page load. A cached response can be valid forever, <a href="#cache-fetch-responses-for-a-specific-amount-of-time">invalidated after a specified amount of time</a>, or <a href="#clear-fetch-cache-on-demand">invalidated on demand</a>.</p>

<h3 id="html-caching">HTML Caching</h3>
<p>HTML for <a href="#static-pages-as-cache">static pages</a> is pre-rendered during the build and stored in the “Full Route Cache” as part of the build artifacts. This HTML is returned on initial page load to quickly show the content of the page. Client side components are then re-hydrated to finish building the page.</p>

<p>Just like fetch() caching, cached HTML can be valid forever, invalidated after a specified amount of time, or invalidated on demand depending how it is configured. The Next.js server renders and caches HTML at runtime in cases where the cache is configured to expire.</p>

<h3 id="react-server-component-payloads-rsc">React Server Component Payloads (RSC)</h3>
<p>React Server Component Payloads (RSC) are also generated at build time or run-time on the server. Cached RSC payloads work very similarly to cached HTML, but are used for subsequent Single Page Application (SPA) style page navigations.  After the initial full page load, Next.js navigates to new pages by using the browser’s History API and DOM manipulation.</p>

<p class="aside wide info"><strong>What is a RSC payload?</strong> RSC stands for React Server Component. RSC payloads describe everything about a page and contain pre-rendered Server Components, client component placeholders and props passed to client components from the server. Next.js uses this data to hydrate new pages without doing a full page load.</p>

<p>RSC payloads are cached by two mechanisms depending on where the payload is cached:</p>

<h4 id="the-server">The Server</h4>
<p>The Full Route Cache stores RSC payloads and pre-rendered HTML. The server uses it to quickly return pre-built RSC payloads to the browser. Those payloads are generated at build time for static pages. For pages that are re-validated periodically, the payload is rebuilt and stored in the cache at runtime by the Next.js server.</p>

<h4 id="the-browser">The Browser</h4>
<p>RSC payloads are the one data type that Next.js also caches in the browser. These payloads are stored in the Next.js Router Cache. The Router Cache re-uses RSC payloads when going back and forward through history and when navigating to a new page whose RSC payload has been pre-fetched. No request is made to the server when the cache has the necessary payload.</p>

<h3 id="static-pages-as-cache">Static Pages as Cache</h3>
<p>When coming from other JavaScript frameworks, Next.js Static Pages are rather confusing. Static pages effectively work as a cache but they cache the final HTML not just the data that was used to build the page.</p>

<p>To add to the confusion, these static pages are executed and cached at build time. Next.js automatically opts in pages to static caching when it thinks they do not use any dynamic inputs. As an example, this basic Next.js page is static, resulting in surprising behavior:</p>

<figure class="code " id="code-408">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">export default async function Page() {
    return &lt;div&gt;
        &lt;h1&gt;This page is statically rendered&lt;/h1&gt;
        &lt;div&gt;
            {/* The rendered date will never change! */}
            {/* In fact, the date displayed will be the date the application was built! */}
            Rendered at: {new Date().toString()}
        &lt;/div&gt;
    &lt;/div&gt;
}
</code></pre>
        
        <figcaption><a href="#code-408">page.tsx - Next.js static page by default. Unexpected results!</a></figcaption>
        
    </div>
</figure>

<h2 id="common-caching-examples">Common Caching Examples</h2>
<p>Although Next.js tries its best to make the best caching choice for each page, it will be wrong sometimes. In those cases, you can specify explicitly how you would like pages and data cached.</p>

<h3 id="how-to-force-a-page-to-render-dynamically">How to Force a Page To Render Dynamically</h3>
<p>A page can be configured to render dynamically even when Next.js heuristics have determined it should be a static page:</p>

<figure class="code " id="code-412">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">// Tell Next.js to always render this page dynamically
export const dynamic = &#39;force-dynamic&#39;

export default async function Page() {
    return &lt;div&gt;
        &lt;h1&gt;This page is dynamically rendered&lt;/h1&gt;
        &lt;div&gt;
            {/* The rendered date will update every page load! */}
            Rendered at: {new Date().toString()}
        &lt;/div&gt;
    &lt;/div&gt;
}
</code></pre>
        
        <figcaption><a href="#code-412">page.tsx - Next.js page forced to render dynamically</a></figcaption>
        
    </div>
</figure>

<h3 id="how-to-force-a-page-to-render-statically">How to Force a Page To Render Statically</h3>
<p>Forcing a page to render statically is much less common, but Next.js has a configuration for it when needed:</p>

<figure class="code " id="code-152">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">// Tell Next.js to always render this page statically
export const dynamic = &#39;force-static&#39;
</code></pre>
        
        <figcaption><a href="#code-152">Place in page.tsx - Next.js page forced to render statically</a></figcaption>
        
    </div>
</figure>

<h3 id="how-to-re-render-a-static-page-after-a-specified-time">How to Re-render a Static Page After A Specified Time</h3>
<p>Some pages don’t need up to the second updates, but just need to be refreshed occasionally. Next.js can be told to occasionally refresh a static page.</p>

<figure class="code " id="code-201">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">// Tell Next.js to re-render this static page every hour
// In seconds, 60 sec * 60 minutes/hr = 3600 seconds
export const revalidate = 3600
</code></pre>
        
        <figcaption><a href="#code-201">Place in page.tsx - Next.js page forced to render statically</a></figcaption>
        
    </div>
</figure>

<h3 id="how-to-rerender-a-static-page-on-demand">How to Rerender a Static Page On Demand</h3>
<p>Next.js clears the cached page HTML and RSC payloads and re-renders them when a page’s path is revalidated.</p>

<p class="aside info"><strong>Note</strong> Caches cannot be cleared inside render functions. <code>revalidatePath()</code> must be called inside a <em>Server Action</em> or <em>Route Handler</em>.</p>

<figure class="code " id="code-139">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">// Inside a Server Action or Route Handler
revalidatePath(&#39;/path/to/static/page&#39;)
</code></pre>
        
        <figcaption><a href="#code-139">Forcing a static page to re-render using revalidatePath()</a></figcaption>
        
    </div>
</figure>

<h3 id="how-to-enable-fetch-caching">How to Enable <code>fetch()</code> Caching</h3>
<p>Starting in Next.js 15, fetch requests are un-cached by default. You must opt into caching even when making GET requests. In Next.js 14 and below, only GET requests were cached by default. Use the <code>'force-cache'</code> option when making a request that should be cached:</p>

<figure class="code " id="code-350">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">export const dynamic = &#39;force-dynamic&#39;

export default async function Page() {
    var response = await fetch(&#39;https://api.example.com/...&#39;, { cache: &#39;force-cache&#39; })
    var data = await response.json()

    return &lt;div&gt;
		    This fetched data is cached by Next.js: {data.exampleString}
    &lt;/div&gt;
}
</code></pre>
        
        <figcaption><a href="#code-350">page.tsx - Enable fetch cache with "force-cache"</a></figcaption>
        
    </div>
</figure>

<h3 id="how-to-disable-fetch-caching">How to Disable <code>fetch()</code> Caching</h3>
<p>Some requests should never be cached. Use the <code>'no-store'</code> cache setting to disable caching for that request:</p>

<figure class="code " id="code-164">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">// Tell Next.js to never cache this fetch request
var response = await fetch(&#39;https://api.example.com/...&#39;, { cache: &#39;no-store&#39; })
</code></pre>
        
        <figcaption><a href="#code-164">Uncached fetch request in Next.js</a></figcaption>
        
    </div>
</figure>

<h3 id="how-to-cache-fetch-responses-for-a-specific-amount-of-time">How to Cache <code>fetch()</code> Responses For A Specific Amount of Time</h3>
<p>Requests don’t have to be cached forever. Next.js can cache requests for a certain amount of time before re-fetching them. The Next specific <code>revalidate</code> setting specifies the lifetime of the cached response:</p>

<figure class="code " id="code-223">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">// Tell Next.js to cache this fetch request for a minute
// The setting is in seconds
var response = await fetch(&#39;https://api.example.com/...&#39;, { next: { revalidate: 60 } })
</code></pre>
        
        <figcaption><a href="#code-223">Mark the fetch response as stale after 60 seconds</a></figcaption>
        
    </div>
</figure>

<h3 id="how-to-clear-fetch-cache-on-demand">How to Clear <code>fetch()</code> Cache On Demand</h3>
<p>Sometimes your application knows when data has become stale. In those cases, you can tell Next.js to clear the cache when needed. You must tell Next.js which cached data should be cleared. Data can be cleared by the path of the page or by tags placed in the fetch requests.</p>

<h4 id="how-to-clear-cached-data-by-page">How to Clear Cached Data By Page</h4>
<p>All cache used by a page can be cleared using the page’s path.</p>

<p class="aside info"><strong>Note</strong> Caches cannot be cleared inside render functions. <code>revalidatePath()</code> must be called inside a <em>Server Action</em> or <em>Route Handler</em>.</p>

<figure class="code " id="code-282">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">// 1. Make a cached fetch() request on page &#39;/my/page&#39;
var response = await fetch(&#39;https://api.example.com/...&#39;, { cache: &#39;force-cache&#39; })

// 2. Later, inside a Server Action or Route Handler
revalidatePath(&#39;/my/page&#39;)
</code></pre>
        
        <figcaption><a href="#code-282">Mark all data used by the page as stale using revalidatePath()</a></figcaption>
        
    </div>
</figure>

<h4 id="how-to-clear-cached-data-by-tag">How to Clear Cached Data By Tag</h4>
<p>Clearing by tags lets you re-fetch a type of data regardless of what page it was loaded on.</p>

<p class="aside info"><strong>Note</strong> Caches cannot be cleared inside render functions. <code>revalidateTag()</code> must be called inside a <em>Server Action</em> or <em>Route Handler</em>.</p>

<figure class="code " id="code-299">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">// 1. Make a cached fetch() request on any page
var response = await fetch(&#39;https://api.example.com/...&#39;, { cache: &#39;force-cache&#39;, next: { tags: [&#39;example-api&#39;] } })

// 2. Later, inside a Server Action or Route Handler
revalidateTag(&#39;example-api&#39;)
</code></pre>
        
        <figcaption><a href="#code-299">Mark all tagged data as stale using revalidateTag()</a></figcaption>
        
    </div>
</figure>

<h4 id="how-to-clear-all-cached-data">How to Clear All Cached Data</h4>
<p>The entire Next.js cache can be cleared by revalidating the root path:</p>

<figure class="code " id="code-123">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">// Inside a Server Action or Route Handler
// Clear ALL cached data
revalidatePath(&#39;/&#39;)
</code></pre>
        
        <figcaption><a href="#code-123">Revalidate the entire Next.js cache</a></figcaption>
        
    </div>
</figure>

<h3 id="how-to-cache-data-when-not-using-fetch">How to Cache Data When Not Using <code>fetch()</code></h3>
<p>Next.js can cache data even when <code>fetch()</code> is not used to retrieve it. This is commonly used for database requests. Code must opt-in to this caching by using the react <code>cache()</code> function:</p>

<figure class="code " id="code-286">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">import { cache } from &#39;react&#39;
import { expensiveCall } from &#39;lib&#39;

// Wrap our expensive call in react&#39;s cache function
export const getItem = cache(async (id: string) =&gt; {
  const item = await expensiveCall(id)
  return item
})
</code></pre>
        
        <figcaption><a href="#code-286">code.ts - Cache expensive calls with the cache() function</a></figcaption>
        
    </div>
</figure>

<h2 id="common-problems-with-caching-in-nextjs">Common Problems with Caching in Next.js</h2>
<p>If this post didn’t make it obvious, Next.js caching is pretty complex. This complexity causes silent errors in the form of stale content from over caching and poorly performing pages from under caching. Some of these problems are pretty easy to stumble into:</p>

<h3 id="changes-to-caching-behavior-in-nextjs-15">Changes To Caching Behavior in Next.js 15+</h3>
<p>Next.js version 14 and below cached fetches and GET route handlers by default. This resulted in unintuitive default behaviors. Because of developer confusion, version 15+ does not cache fetches or route handlers by default. Fetch calls and route handlers must now <a href="#enable-fetch-caching">opt-in to caching</a>.</p>

<h3 id="users-see-stale-pages-and-data-in-nextjs">Users See Stale Pages And Data in Next.js</h3>
<p>Stale data will be displayed to users at least some of the time when a page uses any of Next.js’s caching features. This is caused by two Next.js caching behaviors. First, stale data is only checked when a page is requested. And second, to refresh stale data, Next.js returns the stale data for the current request and fills the cache with new data later in the background. <a href="#how-does-nextjs-handle-stale-cache">Read more</a></p>

<h3 id="nextjs-pages-rendering-statically">Next.js Pages Rendering Statically</h3>
<p>All pages are treated as static by default. Next.js has heuristics to detect dynamic pages, but they are not perfect. When the heuristics fail, Next.js renders static pages at build time and never again. Here’s an <a href="#static-pages-as-cache">example of a static page causing unexpected results</a>. This issue is resolved with <a href="#force-a-page-to-render-dynamically"><code>'force-dynamic'</code> which tells the page to render dynamically</a>.</p>

<h3 id="nextjs-always-caches-duplicate-fetch-requests-in-a-single-page-load">Next.js Always Caches Duplicate Fetch Requests in a Single Page Load</h3>
<p>In rare instances, a page makes the same request multiple times, expecting all requests to be sent to the server. Next.js’s “Request Memoization” cache will always cache all requests after the first!</p>

<figure class="code " id="code-479">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">export const dynamic = &#39;force-dynamic&#39; // Always render dynamically

export default async function Page() {
    // This request is not cached by default (In Next.js 15)
    var response = await fetch(&#39;https://api.example.com/data&#39;)

    // Request Memoization will always return the cached result from the first request
    var response2 = await fetch(&#39;https://api.example.com/data&#39;)

    return &lt;div&gt;I fetched some data!&lt;/div&gt;
}
</code></pre>
        
        <figcaption><a href="#code-479">page.tsx - Request Memoization cannot be disabled</a></figcaption>
        
    </div>
</figure>

<p>Avoid ALL Next.js fetch caching by using the unwrapped fetch:</p>

<figure class="code " id="code-200">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">var response = await fetch._nextOriginalFetch(&#39;https://api.example.com/data&#39;)
var response2 = await fetch._nextOriginalFetch(&#39;https://api.example.com/data&#39;)
</code></pre>
        
        <figcaption><a href="#code-200">It is possible to avoid all Next.js caching</a></figcaption>
        
    </div>
</figure>

<h3 id="nextjs-making-requests-for-every-visible-link">Next.js Making Requests For Every Visible Link</h3>
<p>Next.js has very aggressive client side link prefetching. All links visible in the browser’s viewport are prefetched from the server. This can result in a large number of requests being made as soon as a page finishes loading. While beneficial for</p>

<p class="aside warn"><strong>Remember the Prod Build!</strong> This behavior does not occur in development mode.</p>

<figure class="code " id="code-402">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">export default async function Page() {
    return &lt;div&gt;
        &lt;h1&gt;All these links will be prefetched by Next.js as soon as the page finishes loading!
        &lt;div&gt;
            &lt;Link href=&quot;/here&quot;&gt;go here&lt;/Link&gt;
            &lt;Link href=&quot;/there&quot;&gt;go there&lt;/Link&gt;
            &lt;Link href=&quot;/everywhere&quot;&gt;go everywhere&lt;/Link&gt;
        &lt;/div&gt;
    &lt;/div&gt;
}
</code></pre>
        
        <figcaption><a href="#code-402">page.tsx - Links are prefetched when they become visible</a></figcaption>
        
    </div>
</figure>

<h3 id="nextjs-production-builds-behave-differently-than-development">Next.js Production Builds Behave Differently than Development</h3>
<p>When running in development mode, Next.js does not cache as heavily as in the production build. This can cause a page to work perfectly during development but then be cached too heavily when run in production. Be sure to test caching behaviors in Next.js production builds when testing cache behaviors.</p>

<figure class="code " id="code-286">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-bash"># Running in development mode is quick and easy, but doesn&#39;t show full caching behavior:
npm run dev
# -or-
next dev

# Running a product build will enable all Next.js&#39;s caching features:
npm run build
npm run start
# -or-
next build
next start
</code></pre>
        
        <figcaption><a href="#code-286">Next development mode vs production build</a></figcaption>
        
    </div>
</figure>

<h2 id="build-nextjs-apps-with-confidence">Build Next.js Apps with Confidence!</h2>

<p>Bugs are inevitable, even in modern frameworks with fancy caching features. Having a system to automatically capture and report errors when they happen is key to having confidence in your development cycle, and your products.</p>

<p><a href="https://docs.trackjs.com/browser-agent/integrations/nextjs14/">TrackJS error monitoring fully supports Next.js</a>, and all other JavaScript technologies. Built by developers for developers.</p>

      ]]></content:encoded>
    </item>
  
    
    
    
    <item>
		  <title>Debugging: &quot;Failed to construct &apos;Request&apos;: Invalid Argument.&quot; in Edge</title>
		  <link>https://trackjs.com/blog/debugging-edge-failed-to-construct-request-invalid-argument/</link>
      <guid isPermaLink="true">https://trackjs.com/blog/debugging-edge-failed-to-construct-request-invalid-argument/</guid>
      <pubDate>Wed, 26 Jun 2024 00:00:00 +0000</pubDate>
		  <dc:creator><![CDATA[]]></dc:creator>
      <media:content url="https://trackjs.com/assets/images/blog/2019-09-debugging-edge-failed-to-construct.png" medium="image" type="image/png" height="1000" width="2000" />
      <enclosure url="https://trackjs.com/assets/images/blog/2019-09-debugging-edge-failed-to-construct.png" type="image/png" />
      <category><![CDATA[debugging]]></category><category><![CDATA[javascript]]></category>
			<description><![CDATA[Nothing changed in your code. All of a sudden, a tidal wave of errors start happening for Microsoft Edge users. What the heck happened?

On August 28th, 2019, many TrackJS customers saw a sudden surge in errors from Microsoft Edge browsers: Failed to construct &#39;Request&#39;: Invalid Argument and Failed to execute &#39;fetch()&#39; on &#39;Window&#39;: Invalid argument&quot;.

]]></description>
      <content:encoded><![CDATA[
        <div><img src="https://trackjs.com/assets/images/blog/2019-09-debugging-edge-failed-to-construct.png" alt="Debugging: "Failed to construct 'Request': Invalid Argument." in Edge" class="webfeedsFeaturedVisual" /></div>
        <p>Nothing changed in your code. All of a sudden, a tidal wave of errors start happening for Microsoft Edge users. What the heck happened?</p>

<p>On August 28th, 2019, many TrackJS customers saw a sudden surge in errors from Microsoft Edge browsers: <code>Failed to construct 'Request': Invalid Argument</code> and <code>Failed to execute 'fetch()' on 'Window': Invalid argument"</code>.</p>

<!--more-->

<p class="aside">Our <strong><a href="/blog/tag/debugging/">Debugging blog series</a></strong> explores symptoms, causes, and solutions to common JavaScript errors.</p>

<p>The spark that set off this error was a change in the Facebook sdk. Specifically, this snippet of code from their <code>sdk.js</code>:</p>

<figure class="code " id="code-156">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">fetch(url, {
    referrerPolicy:&quot;origin&quot;,
    mode:&quot;cors&quot;,
    credentials:&quot;include&quot;
});
</code></pre>
        
        <figcaption><a href="#code-156">Code snippet adapted from https://connect.facebook.net/en_US/sdk.js</a></figcaption>
        
    </div>
</figure>

<p>In this of call to <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax"><code>fetch</code></a>, facebook is passing <code>referrerPolicy</code>. However, as noted in the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Browser_compatibility">compatibility table</a>, the <code>referrerPolicy</code> has “No Support” on Microsoft Edge.</p>

<p><strong>“No Support” is an understatement.</strong></p>

<h2 id="root-cause">Root Cause</h2>

<p><code>fetch</code> can be called with a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Request"><code>Request</code></a> object. However, all known versions of Microsoft Edge, through 18.18975, this will throw an error if <code>referrerPolicy</code> is passed as an option.</p>

<p>For example, this snippet of code will throw an error in Edge:</p>

<figure class="code " id="code-174">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">var req = new Request(&quot;https://example.com/&quot;, {
    referrerPolicy: &quot;origin&quot;
});
//=&gt; Failed to construct &#39;Request&#39;: Invalid Argument.
</code></pre>
        
        <figcaption><a href="#code-174">Code snippet that breaks Microsoft Edge</a></figcaption>
        
    </div>
</figure>

<p>The <code>referrerPolicy</code> property was added to the standard in June 2018, so it is understandable that Edge does not yet support it. However, the fact that Edge throws indicates that Microsoft’s implementation of <code>Request</code> is leaking out.</p>

<p>We have opened an issue with Microsoft about this issue. However, as they will soon be <a href="https://www.zdnet.com/article/heres-microsofts-updated-roadmap-for-chromium-based-edge-features-for-the-enterprise/">switching to the Chromium engine</a>, we expect it to go unresolved.</p>

<p>This bug may also manifest as the error <strong><code>Failed to execute 'fetch()' on 'Window': Invalid argument.</code></strong> if you pass the <code>referrerKey</code> option to <code>fetch</code> directly. This error will not be thrown, however, it will come as a Promise rejection.</p>

<h2 id="workaround">Workaround</h2>

<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy">ReferrerPolicy</a> specifies whether the current URL will be provided on the <code>Referrer</code> header for the request. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#Examples">MDN has some great examples</a> of how this will be applied.</p>

<p>While Facebook will need to resolve the issue on their sdk, you may still have this error in your own code. Fortunately, the most common use cases for the policy can still be achieved.</p>

<h3 id="option-1-dont-use-request">Option #1: Don’t Use <code>Request</code></h3>

<p>The issue only manifests itself using the <code>Request</code> object as an argument to <code>fetch</code>. You can pass the same parameters directly the <code>fetch</code>. For example,</p>

<figure class="code " id="code-81">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">fetch(new Request(&#39;/&#39;, {/*options*/}));
// becomes =&gt;
fetch(&#39;/&#39;, {/*options*/});
</code></pre>
        
    </div>
</figure>

<h3 id="option-2-use-default-referrerpolicy">Option #2: Use Default <code>referrerPolicy</code></h3>

<p>Simply don’t set the <code>referrerPolicy</code> for the request. This will default to the page’s policy and send along appropriate information. You will need to set the <code>referrerPolicy</code> header appropriate when serving your web pages.</p>

<h3 id="option-3-explicit-set-referrer">Option #3: Explicit Set Referrer</h3>

<p>Rather than relying on the policy to decide what to send as the origin value, you can control how much information is passed along with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax"><code>referrer</code></a> property.</p>

<p>For example, if you want to replicate the <code>"origin"</code> referrerPolicy, you can use the following code to strip path information out of the referrer by setting it to the root of your domain.</p>

<figure class="code " id="code-85">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">fetch(&quot;https://example.com/&quot;, {
    referrer: &quot;/&quot;
});
</code></pre>
        
        <figcaption><a href="#code-85">Replicate Origin ReferrerPolicy</a></figcaption>
        
    </div>
</figure>

<p>Another example, if you want to replicate the <code>"no-referrer"</code> referrerPolicy, you can remove the referrer all together by setting it to empty string.</p>

<figure class="code " id="code-84">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">fetch(&quot;https://example.com/&quot;, {
    referrer: &quot;&quot;
});
</code></pre>
        
        <figcaption><a href="#code-84">Replicate Origin ReferrerPolicy</a></figcaption>
        
    </div>
</figure>

<hr />

<p>Browsers and third-parties can create errors on your website when you least expect them. <a href="/">Production error monitoring from TrackJS</a> will let you know when someone breaks your site. Give it a try and let’s build a better web, together.</p>


      ]]></content:encoded>
    </item>
  
    
    
    
    <item>
		  <title>Error Monitoring on Client- and Server-Side in NextJS 14+</title>
		  <link>https://trackjs.com/blog/error-monitoring-client-and-server-in-nextjs-14/</link>
      <guid isPermaLink="true">https://trackjs.com/blog/error-monitoring-client-and-server-in-nextjs-14/</guid>
      <pubDate>Mon, 06 May 2024 00:00:00 +0000</pubDate>
		  <dc:creator><![CDATA[]]></dc:creator>
      <media:content url="https://trackjs.com/assets/images/blog/2024/error-monitoring-client-and-server-in-nextjs/error-monitoring-nextjs.png" medium="image" type="image/png" height="1000" width="2000" />
      <enclosure url="https://trackjs.com/assets/images/blog/2024/error-monitoring-client-and-server-in-nextjs/error-monitoring-nextjs.png" type="image/png" />
      <category><![CDATA[trackjs]]></category><category><![CDATA[error-monitoring]]></category><category><![CDATA[javascript]]></category>
			<description><![CDATA[Error handling in all the places that NextJS applications can fail.]]></description>
      <content:encoded><![CDATA[
        <div><img src="https://trackjs.com/assets/images/blog/2024/error-monitoring-client-and-server-in-nextjs/error-monitoring-nextjs.png" alt="Error Monitoring on Client- and Server-Side in NextJS 14+" class="webfeedsFeaturedVisual" /></div>
        <p>NextJS is the hot JavaScript framework right now, and like all JavaScript, it can cause quite a few bugs on both the client- and server-side of your applications.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2024/error-monitoring-client-and-server-in-nextjs/so-hot-right-now.jpg" loading="lazy" width="620" height="497" alt="NextJS is so hot right now" />
  <figcaption>NextJS is so hot right now</figcaption>
  
</figure>

<p>One of the most powerful features of NextJS is enabling you to use your code, templates, and patterns across both the server and the client. NextJS will mostly figure out the most efficient place to run. This is super powerful and makes NextJS applications feel very fast compared to strictly client-side rendered applications.</p>

<p>However, that does mean you need to handle errors and exceptions in both the client-side browser environment as well as the NodeJS server. There are two slightly different ways to make sure you monitor all the ways your NextJS application can fail.</p>

<h2 id="client-side-error-handling-in-nextjs">Client-Side Error Handling in NextJS</h2>

<p>NextJS is built on top of React and utilizes the ErrorBoundary concept to handle errors at different levels of the application. This could be useful if you want to have different error screens for different parts of the app.</p>

<p>Of course, we can’t predict where errors will occur, so we also need a “top level” error handler to catch everything. This is also the best place to plug in <a href="/">an error monitoring service like TrackJS</a>. In NextJS, <a href="https://nextjs.org/docs/app/building-your-application/routing/error-handling" rel="nofollow noopener noreferrer">the top-level error handler is the <code>global-error.tsx</code></a> file in the root of <code>app</code>.</p>

<figure class="code " id="code-525">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">&quot;use client&quot;;

import NextError from &quot;next/error&quot;;
import { useEffect } from &quot;react&quot;;
import { TrackJS } from &quot;trackjs&quot;;

// Setup your error monitoring
TrackJS.install({...});

export default function GlobalError({ error, reset }) {

  useEffect(() =&gt; {
    // Capture an error
    TrackJS.track(error);
  }, [error]);

  return (
    &lt;html&gt;
      &lt;body&gt;
        {/* This is the default Next.js error component. */}
        &lt;NextError statusCode={undefined as any} /&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
        
        <figcaption><a href="#code-525">NextJS global-error.tsx</a></figcaption>
        
    </div>
</figure>

<p>The <code>global-error.tsx</code> file only runs in the client-side environment, so it needs to start with the NextJS <code>"use client"</code> directive.</p>

<hr />

<p>Quick aside, <a href="https://cdn.trackjs.com/agent/v3/latest/t.js">the TrackJS agent</a> has been using the custom <code>"use awesome"</code> directive for over 10 years now. Because, ya know, it’s awesome.</p>

<hr />

<p>Most of this <code>global-error.tsx</code> is the standard boilerplate, with 2 additions: first, the call to <code>TrackJS.install</code>. This is where you can configure your instrumentation of the client-side to gather any additional context for your errors. This code will be run right away, regardless of whether or not an error occurs.</p>

<p>The other addition is the call to <code>TrackJS.track</code> within the <code>useEffect</code>. This get’s called by NextJS when an error actually has occurred in a production environment. Even if your error monitoring automatically captures errors (like TrackJS), sending the errors directly within the <code>useEffect</code> will provide better error data.</p>

<p class="aside wide warn"><strong>Don’t Forget!</strong> The <code>global-error.tsx</code> only runs in production builds. To test out your error handling, you need to build and start the project in production mode.</p>

<h2 id="server-side-error-handling-in-nextjs">Server-Side Error Handling in NextJS</h2>

<p>Some of your application code will also be run on the NodeJS server-side of NextJS. NextJS does a pretty good job of detecting most failures, and when it does, it routes the errors to the client-side where it will be recorded by the <code>global-error.tsx</code> file.</p>

<p>But it doesn’t catch everything.</p>

<p>There are several situations where NextJS can blow up on the server and not return anything to the client. How can you detect when those errors happen?</p>

<p>There is no <code>app.tsx</code> or other root application file to plug into. However, the root <code>layout.tsx</code> file is one of the first things that runs on both client and server, so it’s a great place to add other application-level configuration and calls.</p>

<figure class="code " id="code-292">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-javascript">import type { Metadata } from &quot;next&quot;;
import { TrackJS } from &quot;trackjs-node&quot;;

// Setup your error monitoring
TrackJS.install({...});

export const metadata: Metadata = {...};

export default function RootLayout({ children }: Readonly) {
  return (/*your layout markup*/);
}
</code></pre>
        
        <figcaption><a href="#code-292">NextJS layout.tsx</a></figcaption>
        
    </div>
</figure>

<p>Your <code>layout.tsx</code> file will look much more complicated as it will have lots of general structure and <em>layout</em> for your application. But where I have the call to <code>TrackJS.install</code> is a great place to set up general error handling that will run on the server. An error monitoring system like <a href="/for/node/">TrackJS will attach itself to all the places a NodeJS application can fail</a> and record errors automatically.</p>

<p class="aside wide info"><strong>Note</strong> On the server-side, we use a <code>trackjs-node</code> package instead of the standard browser agent. You probably want to implement <a href="https://docs.trackjs.com/node-agent/installation/#isomorphism">an isomorphic wrapper around both agents to centralize your configuration, which is very easy to do.</a></p>

<h2 id="build-nextjs-apps-with-confidence">Build NextJS Apps with Confidence!</h2>

<p>Bugs are inevitable, even in modern frameworks. Having a system to automatically capture and report errors when they happen is key to having confidence in your development cycle, and your products.</p>

<p><a href="https://docs.trackjs.com/browser-agent/integrations/nextjs14/">TrackJS error monitoring fully supports NextJS</a>, and all other JavaScript technologies. Built by developers for developers.</p>

      ]]></content:encoded>
    </item>
  
    
    
    
    <item>
		  <title>Catch JavaScript Errors from your Shopify Theme</title>
		  <link>https://trackjs.com/blog/shopify-theme-error-monitoring/</link>
      <guid isPermaLink="true">https://trackjs.com/blog/shopify-theme-error-monitoring/</guid>
      <pubDate>Mon, 08 Jan 2024 00:00:00 +0000</pubDate>
		  <dc:creator><![CDATA[]]></dc:creator>
      <media:content url="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-1200.png" medium="image" type="image/png" height="1000" width="2000" />
      <enclosure url="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-1200.png" type="image/png" />
      <category><![CDATA[trackjs]]></category><category><![CDATA[error-monitoring]]></category>
			<description><![CDATA[Did JavaScript break my store? Monitor errors in your Shopify store with TrackJS and ensure the answer is &quot;no&quot;!]]></description>
      <content:encoded><![CDATA[
        <div><img src="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-1200.png" alt="Catch JavaScript Errors from your Shopify Theme" class="webfeedsFeaturedVisual" /></div>
        <!-- TODO JG: Have a "shopify" tag after second post is out? -->

<p>As a Shopify theme gets more fully featured, it is likely that large amounts of JavaScript are being used to improve and expand the user experience. Making theme changes gets more nerve wracking as the amount of code increases. Did my sales go down because <a href="/customers/thehutgroup/">I broke something with the last JavaScript change</a>?</p>

<p>If you’re worried about that next theme publish, it’s time to start monitoring user experiences for JavaScript errors. TrackJS makes error monitoring quick and easy to do!</p>

<h2 id="how-to-monitor-javascript-errors-in-a-live-shopify-store">How To Monitor JavaScript Errors In A Live Shopify Store</h2>
<p>TrackJS is purpose built to <a href="/blog/just-js">monitor errors in production websites</a>. It works with virtually every framework out there and Shopify’s theme system is no exception. This example starts with Shopify’s built-in “Dawn” theme and modifies it to include JavaScript error monitoring. That’s overkill for a simple theme built by Shopify, but the concepts should apply to your custom theme as well.</p>

<p>Installing the TrackJS agent is the only step needed to start tracking errors:</p>

<h4 id="1-get-agent-install-snippet">1. Get Agent Install Snippet</h4>
<p>Copy the “global” agent install snippet from your TrackJS Dashboard’s <a href="https://my.trackjs.com/install">install page</a>.  If using TrackJS Applications, get the application’s snippet from the <a href="https://my.trackjs.com/Account/Applications">application management page</a>.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-trackjs-install-snippet.png" loading="lazy" width="533" height="273" alt="Agent install snippet from the TrackJS Dashboard" />
  <figcaption>Agent install snippet from the TrackJS Dashboard</figcaption>
  
</figure>

<p style="border: 1px solid #3a7b65; border-radius: 4px; background-color: #d0f5e3; padding: 15px 40px; margin: 30px -40px; font-size: 0.95em;">
  <strong>Don't have a TrackJS account yet?</strong> <a href="https://my.trackjs.com/signup">Sign up for a free trial</a> to get started.
</p>

<h4 id="2-insert-snippet-in-theme-layout-install-the-trackjs-agent">2. Insert Snippet In Theme Layout (Install The TrackJS Agent)</h4>
<p>The install snippet needs to be present on all of the store’s pages where JavaScript is run. If most pages contain JavaScript, a commonly used layout is a great place to start. For example, the Dawn theme has a layout found at <code>Layout/theme.liquid</code> which used almost everywhere.</p>

<p>The snippet can be inserted using the Shopify Admin site. In the admin site, navigate to “Online Store -&gt; Themes”, click the current theme’s “…” button and click “Edit Code”.</p>

<p>Add the install snippet to the layout and save:</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-layout-install.png" loading="lazy" width="956" height="486" alt="'Layout/theme.liquid' with TrackJS install snippet" />
  <figcaption>'Layout/theme.liquid' with TrackJS install snippet</figcaption>
  
</figure>

<h4 id="3-verify-the-install">3. Verify The Install</h4>
<p>If your store is not live, test the install by manually sending a JavaScript error. Insert the test code into the same layout, just after the install snippet.</p>

<figure class="code " id="code-165">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-html">
&lt;script&gt;
    console.log(&quot;Testing TrackJS Install&quot;);
    TrackJS.track(&#39;Testing TrackJS in my Shopify theme!&#39;);
&lt;/script&gt;
</code></pre>
        
        <figcaption><a href="#code-165">Send a JavaScript error on every page load</a></figcaption>
        
    </div>
</figure>

<p>The test error will show up in the TrackJS Dashboard if everything is configured correctly:</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-test-install-result.png" loading="lazy" width="1161" height="598" alt="Test error shown in the TrackJS Dashboard" />
  <figcaption>Test error shown in the TrackJS Dashboard</figcaption>
  
</figure>

<p style="border: 1px solid #bf7230; border-radius: 4px; background-color: #fff5cb; padding: 15px 40px; margin: 30px -40px; font-size: 0.95em;">
  <strong>Don't Forget!</strong> Remove this test code when finished.
</p>

<h2 id="know-when-customers-hit-missing-urls">Know When Customers Hit Missing Urls</h2>
<p>The store now has a basic TrackJS setup that captures all JavaScript errors! But we can do more. Why not keep track of any time a customer lands on a missing page? This can be very important in cases where an important affiliate is still using an old product URL.</p>

<h4 id="1-modify-the-main-404-template-or-section">1. Modify The Main 404 Template or Section</h4>
<p>In the Dawn theme, the content for 404 (Not Found) pages is contained in <code>Sections/main-404.liquid</code>. Most themes should have a similar liquid template.
Scripts in this template will run any time a user lands on a missing page:</p>

<figure class="code " id="code-174">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-html">
... existing template here ...

&lt;script&gt;
  window.TrackJS &amp;&amp; TrackJS.track(&quot;Page not found: &quot; + window.location);
&lt;/script&gt;
</code></pre>
        
        <figcaption><a href="#code-174">Track missing pages in 'Sections/main-404.liquid'</a></figcaption>
        
    </div>
</figure>

<h4 id="2-test-a-missing-url">2. Test A Missing URL</h4>
<p>We can easily modify the address in the browser to test:</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-404-page-not-found.png" loading="lazy" width="786" height="383" alt="Shopify page not found" />
  <figcaption>Shopify page not found</figcaption>
  
</figure>

<p>TrackJS should display the error within the minute:</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-not-found-in-trackjs-dashboard.png" loading="lazy" width="1269" height="80" alt="TrackJS Dashboard showing Shopify page not found error" />
  <figcaption>TrackJS Dashboard showing Shopify page not found error</figcaption>
  
</figure>

<h2 id="protect-your-whole-store">Protect Your Whole Store</h2>
<p>Themes may have Layouts and Templates that don’t use the main layout. The Dawn theme has a `gift_card.liquid’ template that doesn’t use the default theme layout for example. This prevents agent installation from a single code snippet. Using a theme snippet to centralize the TrackJS agent config makes it easier to install the agent in multiple layouts or templates:</p>

<h4 id="1-create-a-theme-snippet">1. Create A Theme Snippet</h4>
<p>From the code editor, add a new snippet file to your theme at <code>Snippets/trackjs-install.liquid</code>. Copy the same agent install code used above into the file.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-trackjs-install-liquid.png" loading="lazy" width="533" height="273" alt="'trackjs-install.liquid' theme snippet" />
  <figcaption>'trackjs-install.liquid' theme snippet</figcaption>
  
</figure>

<h4 id="2-render-the-snippet">2. Render The Snippet</h4>
<p>Now the agent can be installed anywhere it is needed with a single line in the <code>&lt;head&gt;</code> tag:</p>

<figure class="code " id="code-194">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-html">
&lt;head&gt;
  &lt;!-- ...existing stuff... --&gt;

  {% render &#39;trackjs-install&#39; %}

  &lt;!-- Other scripts should come after the TrackJS Agent install --&gt;
&lt;/head&gt;
</code></pre>
        
        <figcaption><a href="#code-194">Send a JavaScript error on every page load</a></figcaption>
        
    </div>
</figure>

<p>Our Dawn <code>theme.liquid</code> and <code>Templates\gift_card.liquid</code> now look like this:</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-using-trackjs-install-snippet.png" loading="lazy" width="1033" height="505" alt="'theme.liquid' updated to use a theme snippet" />
  <figcaption>'theme.liquid' updated to use a theme snippet</figcaption>
  
</figure>

<h2 id="make-it-production-ready">Make It Production Ready</h2>
<p>At this point, TrackJS is in a fully supported, production ready configuration. However, there a few more things you may wish to do for extra production polish. The Shopify code editor has some opinions about our current setup:</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/shopify-theme-error-monitoring-third-party-asset-warnings.png" loading="lazy" width="490" height="77" alt="The Shopify code editor does not like third party hosted assets" />
  <figcaption>The Shopify code editor does not like third party hosted assets</figcaption>
  
</figure>

<h3 id="use-the-shopify-cdn">Use The Shopify CDN</h3>
<p>While the TrackJS script agent is served by a globally available CDN, the Shopify CDN can be used as well. This has the advantage of reducing your external site dependencies.</p>

<h4 id="1-download-the-trackjs-agent">1. Download The TrackJS Agent</h4>
<p>Save the <a href="https://cdn.trackjs.com/agent/v3/latest/t.js">TrackJS Agent Script</a> to your computer.</p>

<h4 id="2-add-the-agent-to-theme-assets">2. Add The Agent To Theme Assets</h4>
<p>Add the downloaded <code>t.js</code> file to the theme assets at <code>Assets/t.js</code>. If using Shopify’s online code editor, click “+ Add a new asset” in the Assets folder.</p>

<h4 id="3-update-trackjs-install-snippet">3. Update TrackJS Install Snippet</h4>
<p>Tell the theme to use the new Shopify CDN location:</p>

<figure class="code " id="code-222">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-html">
&lt;!-- Replace this script tag... --&gt;
&lt;script src=&quot;https://cdn.trackjs.com/agent/v3/latest/t.js&quot;&gt;&lt;/script&gt;

&lt;!-- ...With this: --&gt;
&lt;script src=&quot;{{ &#39;t.js&#39; | asset_url }}&quot;&gt;&lt;/script&gt;
</code></pre>
        
        <figcaption><a href="#code-222">Load the TrackJS agent from the Shopify CDN</a></figcaption>
        
    </div>
</figure>

<h3 id="use-async-script-optional">Use Async Script (Optional)</h3>
<p>We do not recommend async script loading the agent, but it can be done if a store’s page performance is the top priority. Enabling async will result in the TrackJS agent missing errors that occur during page load. These errors can be very important because they may be ruining the initial display of the page. Replace the agent install snippet with an async enabled version:</p>

<figure class="code " id="code-267">
    <div class="code-wrap">
        <pre data-copyable=""><code class="language-html">
&lt;script&gt;
    window._trackJs = {
        // Use the same options that were passed to TrackJS.install()
        token: &quot;{YOUR TOKEN HERE}&quot;
    };
&lt;/script&gt;
&lt;script async src=&quot;https://cdn.trackjs.com/agent/v3/latest/t.js&quot;&gt;&lt;/script&gt;
</code></pre>
        
        <figcaption><a href="#code-267">Load the TrackJS agent asyncronously</a></figcaption>
        
    </div>
</figure>

<h2 id="build-with-confidence">Build With Confidence</h2>
<p>Knowing whether you’re changes are affecting your customers takes some of the stress out of JavaScript changes in your themes. Now that TrackJS is installed on your Shopify store, errors experienced by customers will be captured in near realtime. Once captured, TrackJS provides additional context to help respond to and resolve issues before sales are lost or customer satisfaction is impacted. Happy Shopify-ing!</p>

      ]]></content:encoded>
    </item>
  
    
    
    
    <item>
		  <title>Why We Only Support JavaScript</title>
		  <link>https://trackjs.com/blog/just-js/</link>
      <guid isPermaLink="true">https://trackjs.com/blog/just-js/</guid>
      <pubDate>Tue, 19 Dec 2023 00:00:00 +0000</pubDate>
		  <dc:creator><![CDATA[]]></dc:creator>
      <media:content url="https://trackjs.com/assets/images/blog/2023/just-js-1200.png" medium="image" type="image/png" height="1000" width="2000" />
      <enclosure url="https://trackjs.com/assets/images/blog/2023/just-js-1200.png" type="image/png" />
      <category><![CDATA[trackjs]]></category><category><![CDATA[sustainability]]></category><category><![CDATA[error-monitoring]]></category><category><![CDATA[javascript]]></category>
			<description><![CDATA[We are unlikely to add additional language support to TrackJS.  And it&#39;s not just because of our name!]]></description>
      <content:encoded><![CDATA[
        <div><img src="https://trackjs.com/assets/images/blog/2023/just-js-1200.png" alt="Why We Only Support JavaScript" class="webfeedsFeaturedVisual" /></div>
        <p>TrackJS is the best frontend error monitoring tool.  It’s all we do and we do it well.  To keep it simple we have just two different JavaScript agents. One for the browser, and one for Node server environments.</p>

<p>That’s it.</p>

<p>No other languages or platforms are supported.  Just JavaScript.</p>

<h2 id="why-just-javascript">Why Just JavaScript?</h2>
<p>Most of our competitors support error monitoring for multiple languages, and we can certainly understand the initial appeal.  But there’s a few good reasons we don’t, and probably will not, support additional languages in TrackJS.</p>

<h3 id="staying-small">Staying Small</h3>
<p>We’re a small company.  We want to stay that way.  We <em>like</em> it that way.  We can remain small and support our current surface area, so long as it’s just JavaScript.</p>

<p>As an alternative, consider the list of <a href="https://sentry.io/platforms/" rel="nofollow noopener noreferrer">supported platforms for Sentry</a>.  That’s a huge list!  Only a company with loads of employees could support all those platforms with any reasonable degree of competence.</p>

<h3 id="customer-support">Customer Support</h3>
<p>Everybody who works at TrackJS is a <em>very</em> high level JS developer.  All of us. When you <a href="/contact/">contact support</a>, your email goes in to a shared inbox that we all check every day.  There’s no tier 1 support.  There’s no ticketing system.</p>

<p>You will receive a prompt reply to your question from an engineer who knows everything about our product, because they build it.  And everyone who works here has a <em>minimum</em> of <strong>15 years</strong> of web development experience.</p>

<p>We pride ourselves on our customer support.  It’s the best.  Maybe the best of any company you’ve used, in any industry.</p>

<p>It’s that good.</p>

<p>But we can’t keep that level of support up if we add an additional 10 or 20 new languages.  We’re not experts in Rust, so how could we hope to correctly monitor Rust errors, or write a Rust agent?</p>

<h3 id="actionable-uis">Actionable UIs</h3>
<p>Every platform has different characteristics.  Making JavaScript error reports useful from the web requires certain context.  Things like screen size, browser name and version, console telemetry, user activity, etc.  And it’s the same story on other platforms.</p>

<p>The problem is, the useful context you want to see in a JS error report is wildly different than the useful context you want to see in a Python error report.</p>

<p>Oh sure, there’s always some common ground.  The user, the device, their location.  But to make a good actionable UI that works well for Javascript and Python (and everything else) at the time is non-trivial.</p>

<p>In fact, we don’t think it can be done.  Which is why we tailor made our UI for JavaScript errors.  Everything about our UI is built on the idea of helping you find and fix your frontend errors.  Stack trace parsing, sourcemap processing, telemetry gathering.  All of it.</p>

<h3 id="frontend-is-different">Frontend is Different</h3>
<p>Frontend error monitoring is a different paradigm than backend.  With frontend error monitoring you’re trying to capture errors that happen on remote devices out of your control.  Oftentimes running a hodgepodge of incredibly untrustworthy interpreted code.  And the errors often only happen in certain circumstances when users take specific actions.</p>

<p>Then all that data needs to be shipped over the internet back to a central location for processing and analysis.  And there’s <em>a lot</em> of it.  The error volume from frontend can easily be 10x or higher than backend.</p>

<p>Once you’ve got the data, you’ve got to figure out what the heck went wrong.  Because things always go wrong in that kind of wild west environment, especially when end users are involved.  You need all the telemetry and context you can get!</p>

<p>Contrast this with monitoring errors on a server in a language like Java.  You own the hardware, you control all the code, and there are dozens of mature logging platforms for Java.  In most cases you get reasonable stack traces that point right at the offending problem, out of the box.</p>

<p>Heck, at TrackJS, a huge percentage of our code is C#/.Net.  We have a simple logger that pushes everything to an elasticsearch instance.  We wrote a custom frontend to query it in about six seconds.  We’ve never wanted more in 10 years.</p>

<p>The bottom line is you need a different kind of tool for frontend vs. backend error monitoring.</p>

<h3 id="summing-it-up">Summing it Up</h3>
<p>We offer the <a href="https://trackjs.com/blog/best-error-monitoring-tools/">best frontend error monitoring tool around</a>. Seriously, we have the ratings to back that up.  On top of that, we have exceptional customer support.  But it’s all predicated on keeping our scope reasonable. We’d love to monitor all the languages and support every use case, but we’ve chosen a different approach.</p>

<p>We’ve decided to do one thing and do it well.</p>

<p>And that means we only support JavaScript.</p>


      ]]></content:encoded>
    </item>
  
    
    
    
    <item>
		  <title>The Dangers of Using VC Funded Companies</title>
		  <link>https://trackjs.com/blog/dangers-of-vc/</link>
      <guid isPermaLink="true">https://trackjs.com/blog/dangers-of-vc/</guid>
      <pubDate>Mon, 04 Dec 2023 00:00:00 +0000</pubDate>
		  <dc:creator><![CDATA[]]></dc:creator>
      <media:content url="https://trackjs.com/assets/images/blog/2023/dangers-vc-1200.png" medium="image" type="image/png" height="1000" width="2000" />
      <enclosure url="https://trackjs.com/assets/images/blog/2023/dangers-vc-1200.png" type="image/png" />
      <category><![CDATA[trackjs]]></category><category><![CDATA[sustainability]]></category>
			<description><![CDATA[Learn from our mistakes: be careful when using VC funded companies as vendors or important parts of your stack.]]></description>
      <content:encoded><![CDATA[
        <div><img src="https://trackjs.com/assets/images/blog/2023/dangers-vc-1200.png" alt="The Dangers of Using VC Funded Companies" class="webfeedsFeaturedVisual" /></div>
        <p>TrackJS started ten years ago.  To date, the only funding TrackJS ever received was the initial founder investment of $4,500 dollars (a whopping $1,500 per founder). Today, you’d call us a <a href="https://www.investopedia.com/terms/b/bootstrapping.asp">“bootstrapped”</a> business.</p>

<p>We’re proud of that fact.  It means there’s no outside investors. No one to make us build a product we don’t want to build.  And no one that can pull the plug if the growth chart doesn’t look like a hockey stick.</p>

<p>Here’s one example of why that matters.</p>

<h2 id="picking-a-cdn-vendor">Picking a CDN Vendor</h2>
<p>We host a JavaScript agent that our customer’s install on their sites.  Bundling our script is fully supported, but lots of folks prefer to load directly from a CDN.  That way, setup is a cinch, updates are instant, and CDN’s generally have PoPs closer to your users so performance is consistently good.</p>

<h3 id="no-one-ever-got-fired-for-choosing-aws">No One Ever Got Fired for Choosing AWS</h3>
<p>We originally chose AWS Cloudfront as our content delivery platform.  At the time, Cloudfront was very basic but fit the bill.  It served our static content in a geostributed manner with performance that was, charitably, acceptable.  The other nice feature was that you could easily configure it to use an S3 bucket as the content origin.</p>

<h3 id="no-one-ever-got-rich-from-choosing-aws-either">No One Ever Got Rich From Choosing AWS, Either</h3>
<p>As our traffic grew, so did our AWS bill.  And not just a little bit.  At one point our CDN bandwidth bill was bigger than our dedicated server infrastructure bill!  AWS might be reliable, but you pay a premium for everything.</p>

<h3 id="finding-a-new-cdn-vendor">Finding a New CDN Vendor</h3>
<p>At the time there weren’t so many options as today.  There were the big dogs, like Akamai, but you know that story.</p>

<p><strong>TrackJS:</strong> “Tell me you’re an outlandishly expensive enterprise focused company without telling me”</p>

<p><strong>Akamai:</strong> “Contact a sales rep for pricing”</p>

<p>We got to thinking. jQuery was all the rage back then; it was ubiquitous and used by millions of websites.  Surely their developers would make smart technical decisions. Who did jQuery use as a CDN?</p>

<h3 id="maxcdn-the-glory-days">MaxCDN, the Glory Days</h3>
<p>So we started using MaxCDN (née NetDNA). A content delivery network <a href="https://en.wikipedia.org/wiki/Ben_Neumann">founded by a mixed martial arts fighter</a>.  It was simple, easy to configure, and much cheaper than Cloudfront.  They had impressive clients (like jQuery) but they also catered to small businesses like ours.  Their pricing was transparent and the performance was better than Cloudfront.</p>

<p>But nothing lasts forever.</p>

<h4 id="a-dark-cloud-on-the-horizon">A Dark Cloud On The Horizon.</h4>
<p>In 2016 we get an email from the other (presumably less physically imposing) founder.  Here’s some of it.</p>

<blockquote>
  <p>We started MaxCDN with the vision of creating an automated content delivery platform that customers would love… growing to 15,000 customers… We did this all with very little funding.</p>
</blockquote>

<p>Yep, OK.  That’s one reason we liked your company…</p>

<blockquote>
  <p>Out of the blue one day we got a call from Lance Crosby and his team who told us about their vision for StackPath</p>
</blockquote>

<p>Uhh, who?</p>

<blockquote>
  <p>Joining StackPath is a perfect fit for my initial goal and obvious to me that it’s the next step in MaxCDN’s evolution.</p>
</blockquote>

<p>Ope. Yep. So the MaxCDN guy needed a new boat.</p>

<h3 id="who-the-hell-is-stackpath">Who the Hell is StackPath?</h3>
<p>Well I’d never heard of StackPath, but I know an ominous email when I get one.  Naturally the first stop was Crunchbase.  Who are these guys? They came out of nowhere and bought our perfectly functioning CDN provider.</p>

<p>Here’s what their <a href="https://www.stackpath.com/press/stackpath-launches-to-build-the-path-to-a-secure-internet/">launch PR release</a> says:</p>

<blockquote>
  <p>StackPath today emerged from stealth as the Security-as-a-Service company providing a path to a more secure Internet and announced a significant investment from ABRY Partners, a $4.3 billion private equity fund focused on investing in information and business service companies.</p>
</blockquote>

<p>I don’t know anything about stealth (like they’re some kind of damned fighter plane?), but I can see VC/PE funded nonsense a mile away.</p>

<p>And it’s a lot of funds to be sure.  They’ve raised just shy of <strong>$400 million</strong> since 2015.  And unfortunately that means some lofty expectations.  Expectations that our poor CDN will never meet.</p>

<h4 id="living-on-the-edge">Living on the Edge</h4>
<p>We assumed the worst, but for awhile things were fine.  We eventually had to migrate to their new “StackPath CDN”, which was probably just MaxCDN gear with a different domain, but otherwise worked OK. Funny enough, our monthly bill also went down.  Turns out they were burning some of that investor money to keep bandwidth prices extra low.</p>

<p>But then, as inevitable as that hangover after your fourth fireball shot:</p>
<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/stack_path_sucks_balls.png" loading="lazy" width="403" height="449" alt="Closing shop on our CDN" />
  <figcaption>Closing shop on our CDN</figcaption>
  
</figure>

<p>Best cloud computing platform? AWS, Azure, GCP and Cloudflare are all better known and more relevant.  And you know what else?  <strong><em>Every damn one of them has a CDN product.</em></strong></p>

<p>The last paragraph of the announcement is particularly rich: “Akamai Technologies has acquired select StackPath enterprise customer CDN contracts”.  Here’s <a href="https://www.akamai.com/newsroom/press-release/akamai-acquires-stackpath-cdn-customers">the link.</a></p>

<blockquote>
  <p>“We look forward to welcoming these new enterprise customers and providing them immediate access to the agility and scale of Akamai Connected Cloud to create and deliver flawless digital experiences,”</p>
</blockquote>

<p>StackPath sold their most valuable customers for <strong>$20 million</strong> to Akamai and hung the rest out to dry.  For context, that’s just <strong>5%</strong> of the total funding they’ve received so far.</p>

<h3 id="vc-funded-companies-do-weird-things">VC Funded Companies Do Weird Things</h3>
<p>The point of all this is to illustrate why you should always be leery of VC funded companies.  StackPath needed huge returns to appease their investors. Twenty million buckaroos sounds pretty good to me, but when you’re staring down the barrel of <strong>$400 million</strong> investor dollars that need to be multiplied, you make extreme choices.</p>

<p>The net result was that we paid the price.  Not just us, but every other customer they had. We spent hours of our time investigating and then migrating to a new CDN.  Multiply that across all those other customers and you’re looking at thousands of hours wasted, serious operational risks taken, with no real upside for anyone. All because some limited partners wanted their 10x and thought “edge computing” was the buzzword that could deliver it.</p>

<h3 id="choose-sustainable-companies">Choose Sustainable Companies</h3>
<p>When you’re looking for a new vendor, or a new service to use, or a new tool for your stack, you should always factor in the amount of funding a company has taken.  What if the investors want them to pivot? What if they need to shut down due to difficult funding conditions? What if you’re a small customer - will they treat you differently because you don’t help their enterprise value or move the needle on their next funding round?</p>

<p>Let’s say you’re in the market for a frontend error monitoring service; there’s plenty of options out there.  If you go with TrackJS, you don’t have to worry about that stuff.  We’re bootstrapped and profitable.  I wrote even more about the <a href="/blog/profitability-is-important/">importance of profitability</a>, so go read it.</p>

<p>We do frontend error monitoring, and to borrow a famous law firm marketing <a href="https://www.startribune.com/jack-prescott-of-this-is-all-we-do-and-we-do-it-well-tv-pitch-fame-dies/296358961/">slogan</a> from my childhood: <strong>“It’s all we do, and we do it well”</strong></p>

<p><em>Postscript<em></em></em></p>

<p>If you’re wondering who we’re using for our CDN services now, it’s <a href="https://bunny.net/">bunny.net</a>.  They remind us very much of MaxCDN 7 years ago.  So far so good. Now we’re just waiting for the AI generated buzzword laden acquisition email.</p>


      ]]></content:encoded>
    </item>
  
    
    
    
    <item>
		  <title>5 Best Frontend Error Monitoring Tools</title>
		  <link>https://trackjs.com/blog/best-error-monitoring-tools/</link>
      <guid isPermaLink="true">https://trackjs.com/blog/best-error-monitoring-tools/</guid>
      <pubDate>Tue, 14 Nov 2023 00:00:00 +0000</pubDate>
		  <dc:creator><![CDATA[]]></dc:creator>
      <media:content url="https://trackjs.com/assets/images/blog/2023/frontend-error-monitoring-tools/best-frontend-error-monitoring-tools.png" medium="image" type="image/png" height="1000" width="2000" />
      <enclosure url="https://trackjs.com/assets/images/blog/2023/frontend-error-monitoring-tools/best-frontend-error-monitoring-tools.png" type="image/png" />
      <category><![CDATA[error-monitoring]]></category><category><![CDATA[trackjs]]></category><category><![CDATA[javascript]]></category>
			<description><![CDATA[Compare the strengths and weaknesses for Frontend Error Monitoring tools. We&#39;ll look at the focus, features, and pricing of the top contenders so you can pick the platform the works best for you.]]></description>
      <content:encoded><![CDATA[
        <div><img src="https://trackjs.com/assets/images/blog/2023/frontend-error-monitoring-tools/best-frontend-error-monitoring-tools.png" alt="5 Best Frontend Error Monitoring Tools" class="webfeedsFeaturedVisual" /></div>
        <p>You have so many options for frontend error monitoring today, and they all do slightly different things. We looked at everyone and did a breakdown of the most important features for frontend, the problems developers run into, end user reviews, and pricing structures to see how the best vendors stack up.</p>

<style>
  figure.pricing {
    flex-direction: row !important;
  }
  figure.pricing .badge {
    border: 2px solid #212932;
    background-color: #212932;
    color: white;
    border-radius: 4px;
    max-width: 500px;
    margin-right: 10px;
    padding: 20px;
  }
  figure.pricing .badge .value {
    font-size: 2em;
    display: flex;
    justify-content: center;
  }
  figure.pricing .badge .per {
    font-size: 0.8em;
    line-height: 1.2em;
    display: flex;
    justify-content: center;
  }
  figure.pricing .review {
    height: 87px;
  }
  figure.pricing .review img {
    height: 87px;
    width: auto;
  }
</style>

<h2 id="what-is-frontend-error-monitoring">What is Frontend Error Monitoring</h2>

<p>Error Monitoring (sometimes called <strong>Crash Reporting</strong>) is the process of recording errors, exceptions, or problematic events from production software. For most systems, this is done by installing an agent into the software that can listen for errors and send them to a logging service. On the frontend, that means collecting errors from the user’s web browser with JavaScript.</p>

<p>Frontend Error Monitoring is a tricky problem because there is tons of noise coming from robots, malicious users, or unsupported browsers and extensions. On the backend, every error is something real, a failure to be investigated. But on the frontend, we have to sort through the noise to find the real problems.</p>

<p>There are a lot of tools out there that do Frontend Error Monitoring, but very few of them do it well. I am obviously biased in this comparison–I won’t insult you and pretend otherwise. But I think it’s important to highlight the unique challenges of frontend error monitoring specifically, and what you’ll need to be successful.</p>

<h3 id="key-things-to-look-for">Key Things to Look For</h3>

<ul>
  <li>
    <p><strong>Error Telemetry</strong> (sometimes called <em>breadcrumbs</em>) are a series of events that lead up to an error, and they are essential to understanding and fixing errors from your frontend. Let’s face it, JavaScript isn’t the best about error handling, and sometimes the messages we get are less than helpful–looking at you <a href="/blog/script-error-javascript-forensics/"><code>Script Error</code></a>. Telemetry allows us to understand a story about how the user arrived at an error so that we can recreate the situation. <a href="https://news.ycombinator.com/item?id=7523749">TrackJS pioneered Error Telemetry into the industry in 2014</a>.</p>
  </li>
  <li>
    <p><strong>Report Filtering</strong> is the ability to easily focus the error reports, listings, and charts on only certain attributes. For example, “only show me errors from modern Chrome browsers”, or “don’t include errors that contain ‘facebook’ in the stack trace” are really valuable filtering statements. Having the ability to discover and add this kind of filter allows you to discover the errors that impact <em>your most valuable users</em>.</p>
  </li>
  <li>
    <p><strong>Ignore Rules</strong> (sometimes called <em>inbound filters</em>) allow you to filter out the noise from the web. Ignore Rules give you the ability to define errors that should never be captured, like from unsupported browsers, parts of the world you don’t serve, or from that noisy third-party that won’t fix their code. Ignore Rules give you control of your data and your costs.</p>
  </li>
  <li>
    <p><strong>Pricing Structure</strong> varies quite a bit between vendors: some price on seats, others on events, and TrackJS prices on page views. Vendors block different features behind pricing tiers, so be sure to consider the price to get the features you’ll need for your project.</p>
  </li>
</ul>

<h3 id="common-problems">Common Problems</h3>

<ul>
  <li>
    <p><strong>Too Complicated to Use.</strong> With more power comes more complexity. Some tools in the space are really powerful and can do a lot of things–but their UIs tend to turn into spaceship controls along the way. Consider how you will use the tool. If you have an engineer who will be a fulltime monitoring person, then go for the power. If you’ll check it periodically and need to discover answers quickly, a simpler UI might be better for you. Select the tool that you’ll actually use, not the tool you think you need.</p>
  </li>
  <li>
    <p><strong>Slow or Unhelpful Support.</strong> Integrating software is often challenging. A lot of teams end up with bad tools because they can’t get it installed or working correctly. Make sure that the documentation is helpful and correct, and when you need help, there is someone on the other side who actually knows how things work.</p>
  </li>
  <li>
    <p><strong>Unsustainable Vendors.</strong> There is nothing more frustrating then getting a bunch of unplanned work because one of your vendors is closing down. Unfortunately, it’s all too common, for companies to get sold off to private equity, and then closed down. When I look at vendors, I tend to pick ones that are profitable and controlled by engineers.</p>
  </li>
</ul>

<h2 id="frontend-error-monitoring-tools">Frontend Error Monitoring Tools</h2>

<p>I spend <strong>a lot</strong> of time researching tools, and how each of these teams is approaching the problem. I tried to normalize pricing for the cost to monitor 1 million errors with the core front features I mentioned above. I also looked at user reviews on G2 to see what people actually think.</p>

<p>Here are the teams that I think are best positioned to solve Frontend Error Monitoring:</p>

<figure>
  <table>
    <tr>
      <th>Vendor</th>
      <th>Cost for 1M</th>
      <th>User Rating</th>
    </tr>
    <tr>
      <td><a href="#1-trackjs">TrackJS</a></td>
      <td><strong>$99</strong> per month</td>
      <td><strong>4.7</strong> / 5</td>
    </tr>
    <tr>
      <td><a href="#2-rollbar">Rollbar</a></td>
      <td>$175 per month</td>
      <td>4.5 / 5</td>
    </tr>
    <tr>
      <td><a href="#3-sentry">Sentry</a></td>
      <td>$484 per month</td>
      <td>4.5 / 5</td>
    </tr>
    <tr>
      <td><a href="#4-raygun">Raygun</a></td>
      <td>$600 per month</td>
      <td>4.3 / 5</td>
    </tr>
    <tr>
      <td><a href="#5-bugsnag">BugSnag</a></td>
      <td>$549 per month</td>
      <td>4.3 / 5</td>
    </tr>
  </table>
  <figcaption>Frontend Error Monitoring Vendor Comparison</figcaption>
</figure>

<p>Let’s break down each one and cover the strengths and weaknesses, what users think of the product, and about how much it costs.</p>

<h3 id="1-trackjs">1. TrackJS</h3>

<figure class="pricing flex">
  <div class="badge flex-column">
    <div class="value">$99</div>
    <div class="per">per month</div>
  </div>
  <div class="review flex-column">
    <a href="https://www.g2.com/products/trackjs/reviews" rel="nofollow noopener noreferrer">
      <img src="/assets/images/blog/2023/frontend-error-monitoring-tools/g2-47.png" loading="lazy" alt="TrackJS is rated 4.7 on G2" height="200" width="400" />
    </a>
  </div>
</figure>

<p>TrackJS is a frontend-only error monitoring platform that tries to solve the signal-to-noise problems on the web by providing <em>discoverable filtering tools</em> and <em>unlimited ignore capabilities</em>. TrackJS launched from Minnesota, USA in 2013 with the innovative Telemetry Timeline that helped developers understand more of their bugs by giving them context into what happened. This feature was so powerful that most other venders have partially implemented it in their own platforms as “breadcrumbs”.</p>

<p>TrackJS has a fixed-tier pricing model that scales on the number of page views rather than error volume. This aligns the goals of TrackJS to reduce the number of customer errors and grow their business (rather than growing when customers have problems).</p>

<p>TrackJS was bootstrapped by the founders and over 10 years later it remains owned and operated by engineers. The tool focuses on simplicity and solving the problems unique to the frontend.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/frontend-error-monitoring-tools/trackjs-filters.png" loading="lazy" width="1200" height="702" alt="TrackJS Error Filtering" />
  <figcaption>TrackJS Error Filtering</figcaption>
  
</figure>

<h4 id="trackjs-strengths">TrackJS Strengths</h4>

<ul>
  <li><strong>Error Telemetry</strong>. The original TrackJS Telemetry events captured around user actions and network events contain more detail while preventing any sensitive user information from being recorded automatically.</li>
  <li><strong>Unlimited Ignore Rules</strong>. Ignore Rules allow developers to focus on the errors that actually matter. TrackJS allows you to create unlimited rules, as their pricing model does not rely on error volume to be profitable.</li>
  <li><strong>Engineer Owned and Supported</strong>. The engineering team is very engaged with customers and support. When you need help, you always get someone who knows how the system works to help you.</li>
</ul>

<h4 id="trackjs-weaknesses">TrackJS Weaknesses</h4>

<ul>
  <li><strong>Limited Built-in Integrations</strong>. There are few built-in integrations for TrackJS. It only integrates with ChatOps tools like Slack and Teams. Other integrations rely on email or simple API calls that must be built.</li>
  <li><strong>Limited backend platform support</strong>. TrackJS is focused on frontend problems, and it’s backend platform support is limited to only NodeJS platforms.</li>
</ul>

<h4 id="trackjs-pricing">TrackJS Pricing</h4>

<p>TrackJS costs only $99 per month to cover 1 million errors (actually up to 2.8 million errors). TrackJS is priced by page views, allowing unlimited errors for that traffic. This Professional plan allows up to 1 million page views. There are no essential features excluded.</p>

<h4 id="trackjs-is-rated-475-on-g2"><a href="https://www.g2.com/products/trackjs/reviews" rel="nofollow noopener noreferrer">TrackJS is rated <strong>4.7/5</strong> on G2</a>.</h4>

<blockquote>
  <p>TrackJS does exactly what you hope it would do. And a lot more of stuff that you didn’t know you needed.</p>
</blockquote>

<blockquote>
  <p>One of the things I like most about TrackJS is the neat reports. The reports they provide are easy to understand and detailed. It is also relatively easy to setup for a beginner! Coming to pricing, it is quite cheaper than the competition.</p>
</blockquote>

<hr />

<h3 id="2-rollbar">2. Rollbar</h3>

<figure class="pricing flex">
  <div class="badge flex-column">
    <div class="value">$175</div>
    <div class="per">per month</div>
  </div>
  <div class="review flex-column">
    <a href="https://www.g2.com/products/rollbar/reviews" rel="nofollow noopener noreferrer">
      <img src="/assets/images/blog/2023/frontend-error-monitoring-tools/g2-45.png" loading="lazy" alt="Rollbar is rated 4.5 on G2" height="200" width="400" />
    </a>
  </div>
</figure>

<p>Rollbar started in San Francisco back in 2012 as a tool to capture errors from Ruby applications, but has expanded into many other platforms, including frontend error monitoring. They are funded by VC, and have raised $18.5 million.</p>

<p>Rollbar and TrackJS are the only products in this listing that focus on error monitoring. The allows the Rollbar UI to be clean and simple to use. Rollbar has some powerful machine-learning tools that came in via their acquisition of SameBug that allow errors to be automatically grouped together.</p>

<p>However, Rollbar lacks capability around Ignore Rules and filtering, meaning you’ll have to deal with extra noise from errors that don’t really matter to your users.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/frontend-error-monitoring-tools/rollbar.png" loading="lazy" width="1250" height="696" alt="Rollbar Error Monitoring" />
  <figcaption>Rollbar Error Monitoring</figcaption>
  
</figure>

<h4 id="rollbar-strengths">Rollbar Strengths</h4>

<ul>
  <li><strong>Multi-Platform</strong>. Rollbar supports 10 major platforms, so it’s very likely that you’ll be able to gather backend errors in addition to frontend errors with Rollbar. The integration between them is not as good as Sentry’s though.</li>
  <li><strong>Automatic Grouping</strong>. The ML-based error grouping is really quite good in Rollbar. It combines errors that are really referencing the same underlying problem.</li>
  <li><strong>Simple Clean UI</strong>. The UI is clean, yet modern. It surfaces the important aspects of errors well.</li>
</ul>

<h4 id="rollbar-weaknesses">Rollbar Weaknesses</h4>

<ul>
  <li><strong>Noisy Errors</strong>. The lack of Ignore Rule capability is the real problem here. The frontend is definitely not the focus for Rollbar, and you’ll have to deal with a lot of noisy reports and alerts from bots and browser extensions.</li>
  <li><strong>Limited Filtering</strong>. The UI filtering is not the best. It relies on a bespoke syntax in a magic search bar that is difficult to understand and use.</li>
  <li><strong>Complex JavaScript Agent</strong>. Many users complain about the complexity of integrating the JavaScript agent into their code. The Rollbar agent is one of the largest agents at 80KB, and has a lot of configuration options to review.</li>
</ul>

<h4 id="rollbar-pricing">Rollbar Pricing</h4>

<p>Rollbar costs $175 per month to cover a million errors with required features. Rollbar prices their service per error event, so if you have more bugs you’ll have to pay them more. All essential frontend features are included in the “Essentials” plan.</p>

<h4 id="rollbar-is-rated-455-on-g2"><a href="https://www.g2.com/products/rollbar/reviews" rel="nofollow noopener noreferrer">Rollbar is rated <strong>4.5/5</strong> on G2</a>.</h4>

<blockquote>
  <p>Rollbar gives us centralized error management, error mapping with timelines and usage, great user interface, selection of integrations, and dashboards. Tracking of errors across environments is a huge time saver. Some integrations are more painful than others (looking at you, JavaScript, with your errors and required mapping files) - also error grouping can be difficult to get the hang of</p>
</blockquote>

<blockquote>
  <p>Rollbar captures all the JavaScript errors on our websites. Groups occurrences of the same error together. Good reporting. We have lots of errors that we ignore, it would be nice to categorize these more.</p>
</blockquote>

<p class="text-center"><a href="/compare/rollbar-alternative/">Compare Rollbar vs TrackJS</a></p>

<hr />

<h3 id="3-sentry">3. Sentry</h3>

<figure class="pricing flex">
  <div class="badge flex-column">
    <div class="value">$484</div>
    <div class="per">per month</div>
  </div>
  <div class="review flex-column">
    <a href="https://www.g2.com/products/sentry/reviews" rel="nofollow noopener noreferrer">
      <img src="/assets/images/blog/2023/frontend-error-monitoring-tools/g2-45.png" loading="lazy" alt="Sentry is rated 4.5 on G2" height="200" width="400" />
    </a>
  </div>
</figure>

<p>Sentry is a fullstack application monitoring platform built for developers to optimize the performance of their code. Sentry works in most platforms and provides a wide range of services including issue tracking, performance monitoring, session replay, code coverage, and everything under the kitchen sink too.</p>

<p>Sentry was launched in San Francisco in 2012 as an offshoot from the Disqus online commenting platform. It is VC funded, having raised $217 million.</p>

<p>Sentry is an incredibly powerful tool, but the lack of focus leads to a lot of complexity in the UI and the documentation. While it can certainly do everything you would need it to do, you’ll likely need a dedicated monitoring engineer to take advantage of it.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/frontend-error-monitoring-tools/sentry.png" loading="lazy" width="978" height="315" alt="Sentry Application Performance Monitoring" />
  <figcaption>Sentry Application Performance Monitoring</figcaption>
  
</figure>

<h4 id="sentry-strengths">Sentry Strengths</h4>

<ul>
  <li><strong>Fullstack Platform</strong>. Sentry allows you to trace a frontend error down through the backend services and databases needed to fulfil it. If you are part of a fullstack team, this gives you a lot more breadth in your visibility.</li>
  <li><strong>Integrations with other tools</strong>. Sentry has a lot of built in integrations with tools like Jira and GitHub to link you development workflow together.</li>
  <li><strong>Open Source Model</strong>. (Kinda). Most of Sentry is Open Source, so you can see exactly how it works or run it yourself on your own hardware.</li>
</ul>

<h4 id="sentry-weaknesses">Sentry Weaknesses</h4>

<ul>
  <li><strong>Fullstack Platform</strong>. Wait, isn’t that a strength? It’s both. Because Sentry needs to support so many different ideas, the UI is somewhat unfocused and complicated. They don’t have good filtering and grouping concepts, which are critically important for the frontend, but not at all useful for the backend.</li>
  <li><strong>Complex UI and Documentation</strong>. Many users remark how the Sentry UI is complicated to use from all the different things it does. This same complication is visible in their documentation. The learning curve to use Sentry effectively is high.</li>
  <li><strong>High Cost for Required Features</strong>. Some of the most important features, like Server Side Filtering, is locked behind the high-cost “Business” tier.</li>
</ul>

<h4 id="sentry-pricing">Sentry Pricing</h4>

<p>Sentry pricing is complicated with mutliple meters. To cover 1 million errors with required features costs a whopping $484 per month. Sentry is priced per error event–the more errors you have, the more you have to pay. Important frontend features like “server-side filtering” (AKA Ignore Rules) are limited to the Business plan.</p>

<h4 id="sentry-is-rated-455-on-g2"><a href="https://www.g2.com/products/sentry/reviews" rel="nofollow noopener noreferrer">Sentry is rated <strong>4.5/5</strong> on G2</a>.</h4>

<blockquote>
  <p>Sentry is Helpful in monitoring and notification when we have failures in Production / non-Production environments. Very difficult to filter the error.</p>
</blockquote>

<blockquote>
  <p>Sentry gives us detailed information, integration with Jira, can check the exact error in code, but complicated to use.</p>
</blockquote>

<p class="text-center"><a href="/compare/sentry-alternative/">Compare Sentry vs TrackJS</a></p>

<hr />

<h3 id="4-raygun">4. Raygun</h3>

<figure class="pricing flex">
  <div class="badge flex-column">
    <div class="value">$600</div>
    <div class="per">per month</div>
  </div>
  <div class="review flex-column">
    <a href="https://www.g2.com/products/raygun/reviews" rel="nofollow noopener noreferrer">
      <img src="/assets/images/blog/2023/frontend-error-monitoring-tools/g2-43.png" loading="lazy" alt="Raygun is rated 4.3 on G2" height="200" width="400" />
    </a>
  </div>
</figure>

<p>Raygun is a application monitoring platform from New Zealand that’s been around since 2007. It was developed as a product from Mindscape, a .NET developer tooling company, but has expanded into more platforms including the frontend web monitoring space.  Raygun provides Crash Monitoring, Application Performance Monitoring, and <a href="https://requestmetrics.com/">Real User Monitoring</a>.</p>

<p>Raygun offers a wide variety of services to developers, especially in the .NET stack where you can leverage licensing across other tools in their portfolio.</p>

<p>I think one of the most powerful features of Raygun for frontend error monitoring is their error grouping capability, which allows you to normalize errors between languages and browsers easily.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/frontend-error-monitoring-tools/raygun.png" loading="lazy" width="1165" height="523" alt="Raygun Application Performance Monitoring" />
  <figcaption>Raygun Application Performance Monitoring</figcaption>
  
</figure>

<h4 id="raygun-strengths">Raygun Strengths</h4>

<ul>
  <li><strong>Error Grouping</strong>. Raygun has some cool advanced capabilities around custom error groupings that allows things that appear different to be reported together. This can be very powerful for complex and diverse applications.</li>
  <li><strong>Performance Monitoring</strong>. Raygun does performance monitoring alongside it’s error monitoring product (at additional cost). While not strictly beneficial to fixing errors, errors are sometimes the cause of performance degradations.</li>
  <li><strong>Advanced Alert Builder</strong>. Raygun allows you to build custom alerting rules with all kinds of knobs and options to make it whatever you want. This can be a double-edged sword though, as teams will often over-alert themselves.</li>
</ul>

<h4 id="raygun-weaknesses">Raygun Weaknesses</h4>

<ul>
  <li><strong>High Costs</strong>. Despite it’s features, Raygun is the most expensive option in this comparison just for the error monitoring components. The other benefits would need to be a good fit for your organization to be worth the price tag.</li>
  <li><strong>Complicated Ignore Rules</strong>. While Raygun does have ignore rules, they are difficult to use correctly, and have a high cost from irrelevant errors if you get them wrong.</li>
  <li><strong>Filters are Hard to Use</strong>. Filtering reports in Raygun is difficult to discover, using a bespoke syntax with no result hints.</li>
</ul>

<h4 id="raygun-pricing">Raygun Pricing</h4>

<p>Raygun is the most expensive option on this listing, costing $600 per month. Raygun is priced per error event–the more bugs you have, the more you have to pay. The “Team” plan is required to have ignore rules (inbound filters), and you need to account for additional volume to cover 1 million errors.</p>

<h4 id="raygun-is-rated-435-on-g2"><a href="https://www.g2.com/products/raygun/reviews" rel="nofollow noopener noreferrer">Raygun is rated <strong>4.3/5</strong> on G2</a>.</h4>

<blockquote>
  <p>Raygun gives us the ability to match errors in the crash reporter with users and create issues directly from those. Helpful for developers and support alike. The filter system is a bit janky.</p>
</blockquote>

<blockquote>
  <p>Raygun has clean and configurable dashboard and constantly being developed. The most significant disadvantages of Raygun was the way that unpredicted error spikes were handled. Meaning they could eat up all capacity for entire month.</p>
</blockquote>

<p class="text-center"><a href="/compare/raygun-alternative/">Compare Raygun vs TrackJS</a></p>

<hr />

<h3 id="5-bugsnag">5. BugSnag</h3>

<figure class="pricing flex">
  <div class="badge flex-column">
    <div class="value">$549</div>
    <div class="per">per month</div>
  </div>
  <div class="review flex-column">
    <a href="https://www.g2.com/products/bugsnag/reviews" rel="nofollow noopener noreferrer">
      <img src="/assets/images/blog/2023/frontend-error-monitoring-tools/g2-43.png" loading="lazy" alt="BugSnag is rated 4.3 on G2" height="200" width="400" />
    </a>
  </div>
</figure>

<p>BugSnag started in San Francisco in 2012 as a fullstack error monitoring platform, which was very unique at the time. Their product has expanded into the full Application Performance Monitoring space, going after more enterprise customers.</p>

<p>BugSnag was funded by venture capital and acquired by Private Equity holding SmartBear in 2021. Since then, their prices have increased substantially.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/frontend-error-monitoring-tools/bugsnag.webp" loading="lazy" width="1590" height="990" alt="BugSnag Application Performance Monitoring" />
  <figcaption>BugSnag Application Performance Monitoring</figcaption>
  
</figure>

<h4 id="bugsnag-strengths">BugSnag Strengths</h4>

<ul>
  <li><strong>Multi-Platform</strong>. BugSnag supports lots of different platforms, allowing you to align your frontend and backend processes, or grouping your mobile and web data together.</li>
  <li><strong>Responsibility Segmentation</strong>. BugSnag allows you to segment complex systems into different team responsibilities.</li>
  <li><strong>Discarding Errors</strong>. BugSnag allows you to create “Ignore Rules” for errors in what they call discarding. However, they only allow this by browser or app version, so it is pretty limited.</li>
</ul>

<h4 id="bugsnag-weaknesses">BugSnag Weaknesses</h4>

<ul>
  <li><strong>High Costs</strong>. BugSnag is one of the highest priced options in this listing, bug misses a few important capabilities.</li>
  <li><strong>Private Equity</strong>. Since being purchased by Private Equity, costs have risen significantly, and development has slowed. I would be concerned about the sustainability of this product.</li>
  <li><strong>Complicated Setup and Documentation</strong>. Users complain about complicated onboarding, setup, and integration. Their documentation is difficult to navigate.</li>
</ul>

<h4 id="bugsnag-pricing">BugSnag Pricing</h4>

<p>BugSnag prices by seat and error volume, so you need to check the number of people on your dev team in addition to expected error volumes. With the base number of users, it already costs over $580 per month. The “Standard” plan includes all required features, and includes up to 1.5 million events.</p>

<h4 id="bugsnag-is-rated-435-on-g2"><a href="https://www.g2.com/products/bugsnag/reviews" rel="nofollow noopener noreferrer">BugSnag is rated <strong>4.3/5</strong> on G2</a>.</h4>

<blockquote>
  <p>BugSnag is easy to integrate once you get the configuration correct. And the web site for monitoring/reviewing exceptions is fantastic. Getting the configuration correct in some cases was not easy.</p>
</blockquote>

<blockquote>
  <p>BugSnag is very simple to set up and maintain; it has powerful search capabilities and workflow mechanisms, with lots of additions information required. The API can be a little cumbersome and the documentation isn’t awesome at times.</p>
</blockquote>

<p class="text-center"><a href="/compare/bugsnag-alternative/">Compare BugSnag vs TrackJS</a></p>

<hr />

<h2 id="lots-of-other-vendors">Lots of Other Vendors</h2>

<p>There are lots of other vendors in the error monitoring space. It’s the kind of problem that every developer wants to fix, so I see a new service startup every month or so.</p>

<p>Most of them are copy-cats of one of the services above with a slightly different UI. Based on what I found in Wappalyzer and BuiltWith, none of them have significant traction, and I’d be concerned about their ability to scale and support customers.</p>

<h2 id="conclusions">Conclusions</h2>

<p>You have a lot of options when it comes to error monitoring for frontend applications. The right choice will depend on your software, your team, and your preferences for risk.</p>

<p>Do you need to manage frontend and backend systems? Rollbar might be a great choice. Do you want to add lots of extra tools to your toolbelt, and you have the budget to do it? Check out Sentry.</p>

<p>If you need to understand your frontend though, and really cut through the noise to see the errors that really matter, TrackJS is the best rated, cheapest, and most feature-rich option for you.</p>

<p>I know, I was shocked that that conclusion as well 😉. The best way to decide is by seeing your own errors though, so <a href="https://my.trackjs.com/signup">get started with a free trial</a> and see if TrackJS works for you.</p>

      ]]></content:encoded>
    </item>
  
    
    
    
    <item>
		  <title>Profitability is Important</title>
		  <link>https://trackjs.com/blog/profitability-is-important/</link>
      <guid isPermaLink="true">https://trackjs.com/blog/profitability-is-important/</guid>
      <pubDate>Mon, 23 Oct 2023 00:00:00 +0000</pubDate>
		  <dc:creator><![CDATA[]]></dc:creator>
      <media:content url="https://trackjs.com/assets/images/blog/2023/profitability-1200.png" medium="image" type="image/png" height="1000" width="2000" />
      <enclosure url="https://trackjs.com/assets/images/blog/2023/profitability-1200.png" type="image/png" />
      <category><![CDATA[trackjs]]></category><category><![CDATA[sustainability]]></category>
			<description><![CDATA[Unprofitable companies, like Flexport and Splunk, are a risk for their customers because they can radically change or close down entirely. What about TrackJS?]]></description>
      <content:encoded><![CDATA[
        <div><img src="https://trackjs.com/assets/images/blog/2023/profitability-1200.png" alt="Profitability is Important" class="webfeedsFeaturedVisual" /></div>
        <p>A few days ago a memo from logistics company Flexport <a href="https://www.businessinsider.com/flexport-layoffs-ceo-announces-20-workforce-reduction-2023-10">leaked to the media</a> announcing significant layoffs.  Now, a tech company doing layoffs in 2023 is hardly notable. A 20% RIF here and there is almost expected. What <em>is</em> notable is the reason for the layoffs: Flexport is trying to achieve <strong>profitability</strong>.</p>

<h2 id="why-profitability-matters">Why Profitability Matters</h2>
<p>In the leaked letter, Flexport CEO Ryan Petersen <a href="https://www.flexport.com/blog/flexport-ceos-note-to-employees/">says the following</a>:</p>

<blockquote>
  <p>I’ve spoken to more than 100 of our top customers in my first month back as CEO, and … it’s clear that our customers want us to be a profitable company they can rely on to solve important problems in their supply chain.</p>
</blockquote>

<p>So their customers (and presumably prospective customers) are demanding profitability!  My how the times are changing.</p>

<p>When you’re looking for a vendor to help manage your supply chain, you want a company with staying power.  One who’ll be around in a decade, or two, or three.  Changing vendors is always time consuming, expensive and risky. And here Flexport’s customers are telling it: you need to show us you’ll be here in ten years or we don’t feel comfortable doing long-term business with you!</p>

<p>There’s no better signal of company health and longevity than being consistently profitable.  That is - making more money than you spend.  Not some Wall St. non-GAAP version of profitable mind you, but actual positive net income.</p>

<p>But wait - if Flexport isn’t profitable now, how have they been operating and paying <a href="https://www.freightwaves.com/news/flexport-lays-off-600-workers-amid-difficult-freight-market">their 3,200 employees</a> these past 10 years?</p>

<h3 id="straight-vc-cash-homie">Straight VC Cash, Homie</h3>
<p>According to Crunchbase, Flexport has <a href="https://www.crunchbase.com/organization/flexport/company_financials">raised a staggering</a> <strong>$2.4 billion</strong> in investor funding over <strong>18 rounds</strong> of financing.  For ten years they’ve paid their employees not with revenue from operations, but from a horde of VC cash.</p>

<p>The CEO even alludes to it in his letter:</p>

<blockquote>
  <p>With more than $1 billion in net cash, following this change, Flexport is now in a great position to take advantage of the opportunities in front of us to return to profitability as soon as the end of next year.</p>
</blockquote>

<p>So they’re still sitting on over $1 billion in cash. What’s particularly notable is that they’ve already burned more than $1.4 billion of their money pile!  And the money fire won’t die down enough for profitability until <em>the end of <strong>next</strong> year</em>.</p>

<h3 id="the-dangers-of-free-money">The Dangers of Free Money</h3>
<p>One downside of heaps of VC cash is unsustainable business models don’t fail early.  Companies who shouldn’t exist are given millions (or sometimes billions - looking at you Softbank) to prop up bad products or services.  This is sometimes called <a href="https://en.wikipedia.org/wiki/Malinvestment">malinvestment</a>.</p>

<p>When interest rates were low and lots of capital was looking for returns - money was sloshing everywhere, even to garbage companies with silly business models (ahem, scooters).  Instead of that money helping solid businesses grow sustainably, it distorted markets and led to situations where inefficient or bad businesses crowded their way in and destroyed or prevented otherwise sustainable ones from succeeding.</p>

<p>This trend is so pervasive in VC, <a href="https://techcrunch.com/2019/03/26/unicorns-arent-profitable-wall-street-doesnt-care/?guccounter=1">that in 2019</a>:</p>

<blockquote>
  <p>Sixty-four percent of the 100+ companies valued at more than $1 billion to complete a VC-backed IPO since 2010 were unprofitable, and in 2018, <strong>money-losing startups actually fared better</strong> on the stock exchange than money-earning businesses.</p>
</blockquote>

<h3 id="times-are-changing">Times Are Changing</h3>
<p>Fortunately with the move away from near-zero interest rate environments, the VCs appear more circumspect now. I’ve heard it’s a tough fundraising environment and not the torrential rainstorm of free money it was. The stock market might finally start to care about profitability too.</p>

<p>Behold Splunk!</p>

<p>Here is a company, who incredibly, for it’s 10+ years of existence, <strong><a href="https://www.macrotrends.net/stocks/charts/SPLK/splunk/net-income">never once</a> had positive net income</strong>!  Shit, they had <a href="https://finance.yahoo.com/quote/SPLK/balance-sheet/"><em>negative</em> shareholder value</a> for year-end 2023.  This means their liabilities exceeded their assets and shareholders were (on paper) underwater.  To bathe in the public’s largesse for so long and still pretend to be a sustainable business!  Well the music stopped and Splunk couldn’t find a chair.</p>

<p>Yeah, I know the press release made it sound real good - Cisco and Splunk, think of the synergies!  Well how about <a href="https://investor.cisco.com/news/news-details/2023/Cisco-to-Acquire-Splunk-to-Help-Make-Organizations-More-Secure-and-Resilient-in-an-AI-Powered-World/default.aspx">these synergies</a>:</p>

<blockquote>
  <p>Expected to be cash flow positive and gross margin accretive in first fiscal year post close, and non-GAAP EPS accretive in year 2</p>
</blockquote>

<p>That’s a fancy way of saying Splunk ain’t gonna help with profitability. “Non-GAAP EPS accretive in year 2” - words only an M&amp;A lawyer could love.</p>

<p>So anyways, now they’re owned by Cisco [ominous laughter].  If you’re a Splunk customer, I assume you’re already used to the ankle-grabbing prices but just imagine what Cisco is going to do to those enterprise agreements!  Add a couple of zeroes to those invoices, that’s one thing they’re gonna do.</p>

<h3 id="selling-dollars-for-dimes">Selling Dollars for Dimes</h3>
<p>Unprofitable companies like Splunk are a risk.  They might go out of business. They might change, remove or destory their product offerings in order to make more money.  They might raise prices massively when things get tough. They might even get bought by a stagnant “tech” company who’s best days are far behind it and exists only to extract the last ounce of revenue from enterprise customers.</p>

<p>So, when looking at a vendor for anything - be it logistics or JavaScript error monitoring - you should look for profitable companies first!</p>

<h2 id="trackjs-is-profitable">TrackJS is Profitable!</h2>
<p>See, there was a point to this whole post! TrackJS has been profitable for years!  Coming up on a decade now, in fact. We’ve done it by keeping infrastructure costs low (more on that in another post) and keeping our scope reasonable.</p>

<p>We don’t want to be the everything platform - we don’t need hundreds of employees.  We make simple tools that solve real customer problems.  And we’ll be here for the next ten years.</p>

<h3 id="were-bootstrapped-too">We’re Bootstrapped Too</h3>
<p>Profitability isn’t our only trick, either.  We’re also bootstrapped!  There is no outside investor looking for big returns.  There’s no one with our feet to the fire to hit unrealistic growth targets for the sake of their already-rich LPs.</p>

<p>Not a Patagonia vest in sight.</p>

<p>It’s just the people who work here, making solid salaries and building the best JavaScript error monitoring tool there is.</p>


      ]]></content:encoded>
    </item>
  
    
    
    
    <item>
		  <title>Saved Filter Notifications</title>
		  <link>https://trackjs.com/blog/saved-filter-notifications/</link>
      <guid isPermaLink="true">https://trackjs.com/blog/saved-filter-notifications/</guid>
      <pubDate>Tue, 10 Oct 2023 00:00:00 +0000</pubDate>
		  <dc:creator><![CDATA[]]></dc:creator>
      <media:content url="https://trackjs.com/assets/images/blog/2023/saved-filter-1200.png" medium="image" type="image/png" height="1000" width="2000" />
      <enclosure url="https://trackjs.com/assets/images/blog/2023/saved-filter-1200.png" type="image/png" />
      <category><![CDATA[trackjs]]></category><category><![CDATA[alerts]]></category><category><![CDATA[product]]></category>
			<description><![CDATA[Alerts and notifications have been part of TrackJS since the very beginning.  Our standard notification options reflect our desire to keep things simple.  Over time though, our customers have asked to customize their alerts and fine tune them to specific scenarios.  To support that use case, we’re releasing a new kind of notification we’re calling “Saved Filter Notifications”.

]]></description>
      <content:encoded><![CDATA[
        <div><img src="https://trackjs.com/assets/images/blog/2023/saved-filter-1200.png" alt="Saved Filter Notifications" class="webfeedsFeaturedVisual" /></div>
        <p>Alerts and notifications have been part of TrackJS since the very beginning.  Our <a href="https://docs.trackjs.com/notifications/#notification-types">standard notification options</a> reflect our desire to keep things simple.  Over time though, our customers have asked to customize their alerts and fine tune them to specific scenarios.  To support that use case, we’re releasing a new kind of notification we’re calling “Saved Filter Notifications”.</p>

<!--more-->

<h2 id="new-filter-based-alerting">New Filter Based Alerting</h2>
<p>Some customers use TrackJS as an early warning system - if an error with a specific message shows up, it means big trouble and they want to know about it right away.  Other customers want to know if their APIs are acting flaky.  And some want to know if their most important users are having trouble.</p>

<p>There’s dozens of reasons you might want to know when certain errors show up (or start happening more frequently).  To that end we’ve recently launched a new filter based notification system. Now you can tell us which errors you care about, and we can tell you when they start happening!</p>

<h3 id="saving-a-filter">Saving a Filter</h3>
<p>To make use of the new custom notifications, the first thing you need to do is create a filter for the errors you care about.  You can filter based on error message, entry type, URL, user and myriad other facets.  It’s easy - just click on the global filter bar at the top of the screen and make your selections.</p>

<p>After you’ve filtered to the relevant errors, click the “share” dropdown in the upper right hand corner of the UI.  A menu will appear and you can enter a name for your filter.  Click the <strong>Save</strong> button.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/saved-filter-share-dropdown.min.png" loading="lazy" width="399" height="332" alt="Saved Filter Dropdown" />
  <figcaption>Saved Filter Dropdown</figcaption>
  
</figure>

<h3 id="configuring-alerts-for-a-saved-filter">Configuring Alerts for a Saved Filter</h3>
<p>Once saved, you can click the <a href="https://my.trackjs.com/account/notifications/saved-filter">Manage Alerts</a> link from the same “share” dropdown.  You’ll see a list of all your saved filters, and any pre-existing notification channels.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/notification-screen.min.png" loading="lazy" width="781" height="496" alt="Filtered Notifications" />
  <figcaption>Filtered Notifications</figcaption>
  
</figure>

<p>Find the saved filter you want to configure alerts for and click the “+ Add Notification” link.</p>

<h4 id="customizing-notifications">Customizing Notifications</h4>
<p>The new saved filter notifications work using four different strategies (more on that below).  Those strategies are based on a <strong>Lookback Period</strong> and an <strong>Error Threshold</strong>.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/2023/notification-modal.min.png" loading="lazy" width="599" height="650" alt="Notification Modal" />
  <figcaption>Notification Modal</figcaption>
  
</figure>

<p>The <strong>Lookback Period</strong> is how large of a time window to use when considering a filter’s error volume. The <strong>Error Threshold</strong> is the minimum number of errors that must occur during the lookback period before an alert is sent.</p>

<p>For example, if a filter has a high base level of errors, it might make sense to set the error threshold high, and a short lookback window.  This way you’ll need a decent spike in a short period of time to cause an alert.</p>

<p>On the other hand, if there’s a situation where you want an alert any time an error matches a filter - a longer window with a lower threshold will make the alert  more sensitive.</p>

<p>And, like our other notifications, you can notify as many channels at a time as you’d like.</p>

<h2 id="how-it-works">How it Works</h2>

<p>We use four different strategies when determining whether to alert.  Each strategy will decide to trigger or not based on the filter’s error volume over time.  <strong>Two</strong> of the strategies must be triggered in order for a notification to be sent (this cuts down on noise and false positives).  We use the following strategies:</p>

<ul>
  <li><em>Simple Historical Average</em>:  This simple strategy compares the filter’s historical average error volume to the lookback period’s current volume and, if the current volume exceeds a multiple, triggers.</li>
  <li><em>Standard Deviation</em>:  This strategy triggers if a filter’s current error volume is multiple standard deviations higher than the historical volume.</li>
  <li><em>Moving Average</em>:  If the moving average of the error volume in the lookback period is several multiples higher than the historical volume, the strategy is triggered.</li>
  <li><em>ML Detection Strategy</em>:  An ML prediction strategy which finds spikes in time series data based on adaptive kernel density estimations and martingale scores.  If the confidence is over 99.9% the strategy triggers.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>
<p>With the new saved filter based notifications, customers can pick and choose when and why they’re alerted.  Filters can be broad, and capture many errors - or very granular and only trigger in rare conditions.  No matter your use case, it’s likely that a well chosen filter with proper alert settings will ensure you’re notified when anything goes wrong.</p>

<p>We’re always on the lookout for new ways to improve customer experience, so if you’ve got suggestions please <a href="/contact/">let us know!</a></p>

      ]]></content:encoded>
    </item>
  
    
    
    
    <item>
		  <title>The State of Client-Side JavaScript Errors</title>
		  <link>https://trackjs.com/blog/the-state-of-client-side-javascript-errors/</link>
      <guid isPermaLink="true">https://trackjs.com/blog/the-state-of-client-side-javascript-errors/</guid>
      <pubDate>Fri, 11 Aug 2023 00:00:00 +0000</pubDate>
		  <dc:creator><![CDATA[]]></dc:creator>
      <media:content url="https://trackjs.com/assets/images/blog/2015-08-11-the-state-of-client-side-javascript-errors.gif" medium="image" type="image/png" height="1000" width="2000" />
      <enclosure url="https://trackjs.com/assets/images/blog/2015-08-11-the-state-of-client-side-javascript-errors.gif" type="image/png" />
      <category><![CDATA[debugging]]></category><category><![CDATA[javascript]]></category><category><![CDATA[error]]></category>
			<description><![CDATA[As JavaScript has grown more prevalent on the web, so have JavaScript errors. Here are the top JavaScript errors from across the web, aggregated by browser, framework, and issue.]]></description>
      <content:encoded><![CDATA[
        <div><img src="https://trackjs.com/assets/images/blog/2015-08-11-the-state-of-client-side-javascript-errors.gif" alt="The State of Client-Side JavaScript Errors" class="webfeedsFeaturedVisual" /></div>
        <p>As JavaScript has grown more prevalent on the web, so have JavaScript errors. As an error monitoring service, we have a unique perspective on how errors impact the web globally, and we are constantly learning more about how the web breaks. We’re thrilled to share this report today so we can all understand it better, and build a better web.</p>

<p>We produce this report every week, you can check it out anytime via the free <a href="/stats">Global Error Statistics</a> report.</p>

<h2 id="error-volume-by-browser">Error Volume by Browser</h2>

<p>The browser JavaScript runtime is an important part of the client-side error story. Error volume is a function of both the popularity of the browser, as well as how likely it is to produce errors.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/error_by_browser_201508.png" loading="lazy" width="1024" height="727" alt="Error Volume by Browser" />
  <figcaption>Error Volume by Browser</figcaption>
  
</figure>

<p>The browser market leaders of Chrome, Firefox, and Internet Explorer 11 represent a majority of the browser market today, so it follows they report a large portion of errors.</p>

<p>Mobile Safari and older Internet Explorer begin representing the friction building web applications for those platforms. Mobile Safari has challenges debugging, as does the infamous developer tools available for IE8 and 9. While much of the web is pushing forward with new APIs and capabilities, many of us are still fighting the old compatibility problems.</p>

<h2 id="error-volume-by-application-framework">Error Volume by Application Framework</h2>

<p>A majority of client-side JavaScript applications use a framework of some kind to assist in the architecture and structure of the system. Error volume by Framework is a reflection of both popularity of the framework and the patterns exercised its community.</p>

<figure class="border ">
  
  <img src="https://trackjs.com/assets/images/blog/error_by_framework_201508.png" loading="lazy" width="1024" height="631" alt="Error Volume by Framework" />
  <figcaption>Error Volume by Framework</figcaption>
  
</figure>

<p>Angular represents the majority of both usage and errors today, however it seems to create a disproportional amount of errors compared to other frameworks. On the opposite of the spectrum, smaller tools like React and Knockout seem to experience relative low error volumes in our sample.</p>

<h2 id="top-5-javascript-errors">Top 5 JavaScript Errors</h2>
<p>By Number of Applications Affected</p>

<p>The “Big Five”, these specific errors are the most common, measured by the number of unique web applications impacted:</p>

<h3 id="1-script-error">1. Script Error</h3>
<p>The arch nemesis of JavaScript error monitoring, we’ve covered <code>Script Error</code> in depth with our <a href="/blog/script-error-javascript-forensics/">JavaScript Forensics</a> series. Script Error stems from browsers implementing the Same-Origin Policy on your scripts and obfuscating error details.</p>

<h3 id="2-error-loading-script">2. Error Loading Script</h3>
<p>This error is emitted from the very popular <a href="http://requirejs.org/">requireJS</a> library when a script fails to loaded asynchronously. Scripts can fail for many reasons, and it’s always a good practice to check for the existence for the SDK provided by a library before referencing it.</p>

<h3 id="3-undefined-is-not-a-function">3. Undefined is not a function</h3>
<p>The incredibly common reference error, which we have all encountered many times. This is likely due to loss of context when invoking a callback.  Our friend Kyle has the <a href="https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/this%20%26%20object%20prototypes/ch1.md">best explanation of this problem around</a>.</p>

<h3 id="4-cannot-read-property-length-of-undefined">4. Cannot read property ‘length’ of undefined</h3>
<p>You think it’s an array or a string? It’s not. It’s undefined. This all-too-common message results from referencing elements or objects that do not exist. Here’s a <a href="/blog/debugging-cannot-read-property-length-of-undefined/">full rundown on the error from our JavaScript Error knowledge base</a>.</p>

<h3 id="5-object-doesnt-support-this-property-or-method">5. Object doesn’t support this property or method</h3>
<p>We don’t know what object, we don’t know what it is, and we don’t know what it’s named, but it broke. This infuriating error reinforces the ongoing frustration debugging Internet Explorer 8. Calling a restricted function, or accessing an unimplemented property can result in a catastrophic error.</p>

<h2 id="error-rollup-score">Error Rollup Score</h2>
<p>Overall, we can roll-up a score for JavaScript quality today, comparing total sampled page-views with errors. It doesn’t paint a rosy picture.</p>

<figure>
  <div style="color: #eb503e; font-size: 48px;"><strong>0.39</strong></div>
  <figcaption>Errors per Page View</figcaption>
</figure>

<p>39% of page views will experience a JavaScript error. That’s a lot of shopping carts abandoned, searches failed, and customers unhappy.</p>

<p>JavaScript on the web is error prone, but it can get better. Let us help make your web applications better with a <a href="/">free trial of TrackJS.</a> We’ll let you know when your customers have problems with the context to recreate and fix bugs</p>

      ]]></content:encoded>
    </item>
  

  </channel>
</rss>
