<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: scott winter</title>
    <description>The latest articles on DEV Community by scott winter (@scott_winter_77ced0700c92).</description>
    <link>https://dev.to/scott_winter_77ced0700c92</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3822616%2F61a67a6c-e974-4135-883d-5332cc7d26e9.png</url>
      <title>DEV Community: scott winter</title>
      <link>https://dev.to/scott_winter_77ced0700c92</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/scott_winter_77ced0700c92"/>
    <language>en</language>
    <item>
      <title>A Practical Guide to Optimizing a Next.js WebGL Game</title>
      <dc:creator>scott winter</dc:creator>
      <pubDate>Wed, 22 Jul 2026 01:09:43 +0000</pubDate>
      <link>https://dev.to/scott_winter_77ced0700c92/from-the-40s-to-97-optimizing-a-nextjs-webgl-game-b01</link>
      <guid>https://dev.to/scott_winter_77ced0700c92/from-the-40s-to-97-optimizing-a-nextjs-webgl-game-b01</guid>
      <description>&lt;p&gt;A Next.js game can look small while still having an expensive initial load.&lt;/p&gt;

&lt;p&gt;The browser may need to hydrate React, initialize WebGL, parse a rendering engine, decode texture atlases, load a tutorial, and request leaderboard data before the player makes a single move.&lt;/p&gt;

&lt;p&gt;That was the problem with &lt;a href="https://colortilesgame.net" rel="noopener noreferrer"&gt;Color Tiles&lt;/a&gt;, a browser game built with Next.js, React, and PixiJS. During optimization, its mobile Lighthouse score improved from a verifiable 59 to 97, while LCP fell from 11.36 seconds to 2.29 seconds.&lt;/p&gt;

&lt;p&gt;The score is not the main point of this article. The useful part is the process: decide what the player needs for the first playable screen, then move everything else out of that loading path.&lt;/p&gt;

&lt;p&gt;For a more detailed, metric-by-metric account of the optimization work, read the &lt;a href="https://medium.com/@winterscott999/from-the-40s-to-97-how-we-cut-the-initial-load-of-a-next-js-webgl-game-by-nearly-80-d04e803a27ac" rel="noopener noreferrer"&gt;full Color Tiles performance case study on Medium&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Measure the production loading path
&lt;/h2&gt;

&lt;p&gt;Do not optimize a Next.js development server. Development mode includes compilation, source maps, React checks, and other work that does not represent the deployed application.&lt;/p&gt;

&lt;p&gt;Start with a production build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use Chrome DevTools and Lighthouse with one consistent mobile profile:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run Lighthouse and record LCP, Total Blocking Time, CLS, and transfer size.&lt;/li&gt;
&lt;li&gt;Open the Network panel, disable the cache, reload, and sort requests by size.&lt;/li&gt;
&lt;li&gt;Record a Performance trace and inspect the LCP marker and long tasks.&lt;/li&gt;
&lt;li&gt;Use the Coverage panel to find large JavaScript files with substantial unused code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Classify every initial request into two groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Needed now:&lt;/strong&gt; the page shell, game controls, a stable board placeholder, and code required for the first interaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Needed later:&lt;/strong&gt; tutorials, leaderboards, below-the-fold content, result dialogs, and optional compatibility code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This classification is more useful than trying to improve the overall Lighthouse score directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Put the game runtime behind a loading boundary
&lt;/h2&gt;

&lt;p&gt;Keep the route and useful page content server-rendered, but isolate browser-only game code in a Client Component. Canvas and WebGL code should not be part of the server-rendering path.&lt;/p&gt;

&lt;p&gt;If the game has a Start or Play action, load the runtime when the player uses it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ComponentType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GameComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ComponentType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;initialMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GameLoader&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;initialMode&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;initialMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Game&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setGame&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GameComponent&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadGame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./color-tiles-game&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setGame&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ColorTilesGame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Game&lt;/span&gt; &lt;span class="na"&gt;initialMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;initialMode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"game-shell"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loadGame&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Loading...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Play&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a separate JavaScript chunk and keeps PixiJS, level generation, input handling, and game state out of the initial route bundle.&lt;/p&gt;

&lt;p&gt;Reserve the final game dimensions in the loader so the page does not jump when the chunk arrives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.game-shell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;760px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the game must start automatically, use &lt;code&gt;next/dynamic&lt;/code&gt; with &lt;code&gt;ssr: false&lt;/code&gt; instead. The important parts are the same: isolate the browser-only module and render a placeholder with stable dimensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Import only the PixiJS modules the game uses
&lt;/h2&gt;

&lt;p&gt;The original implementation imported the complete legacy bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;PIXI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pixi.js-legacy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The game only needed the WebGL renderer and a small set of display primitives. Replace the package-wide import with module-level imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BaseTexture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Renderer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Texture&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pixi/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pixi/display&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Rectangle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pixi/math&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Sprite&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pixi/sprite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AnimatedSprite&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pixi/sprite-animated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the renderer directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Renderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;antialias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;backgroundAlpha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After changing imports, build again and confirm that the old legacy bundle is no longer present in the game chunk. Also test the oldest browser and device class you support. Removing a Canvas fallback is only correct when WebGL is part of your browser support baseline.&lt;/p&gt;

&lt;p&gt;Reducing the bundle saves more than download bytes. Mobile devices also spend less time parsing, compiling, and executing JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Do not mount tutorial media before it is visible
&lt;/h2&gt;

&lt;p&gt;An animated tutorial is useful, but it should not compete with the board during initial loading.&lt;/p&gt;

&lt;p&gt;First, convert animated GIFs to MP4 or WebM. Video usually compresses animation much more efficiently and gives the browser a better decoding path.&lt;/p&gt;

&lt;p&gt;Second, do not render the video element until its modal is open or its section is close to the viewport:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setVisible&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frameRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLDivElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;frameRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;setVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rootMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;160px 0px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;frameRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"tutorial-frame"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;video&lt;/span&gt; &lt;span class="na"&gt;autoPlay&lt;/span&gt; &lt;span class="na"&gt;loop&lt;/span&gt; &lt;span class="na"&gt;muted&lt;/span&gt; &lt;span class="na"&gt;playsInline&lt;/span&gt; &lt;span class="na"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;source&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/tutorial-demo.mp4"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;video&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"tutorial-placeholder"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give the placeholder the same width and aspect ratio as the video. This keeps CLS stable while removing media download and decoding from the first screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Treat page images and game textures differently
&lt;/h2&gt;

&lt;p&gt;Regular React content can use &lt;code&gt;next/image&lt;/code&gt;, responsive &lt;code&gt;sizes&lt;/code&gt;, lazy loading, and an image CDN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/game-preview.webp"&lt;/span&gt;
  &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"A Color Tiles game board"&lt;/span&gt;
  &lt;span class="na"&gt;fill&lt;/span&gt;
  &lt;span class="na"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 760px) 100vw, 600px"&lt;/span&gt;
  &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sizes&lt;/code&gt; value must reflect the rendered CSS width. Without it, a phone may receive an image intended for a desktop layout.&lt;/p&gt;

&lt;p&gt;PixiJS textures and sprite atlases need a different path because the renderer loads their URLs directly. For those assets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Export WebP or AVIF variants where the target browsers and renderer support them.&lt;/li&gt;
&lt;li&gt;Remove transparent padding that is not used by atlas coordinates.&lt;/li&gt;
&lt;li&gt;Keep atlas dimensions and frame rectangles synchronized.&lt;/li&gt;
&lt;li&gt;Put URL generation behind one helper so the CDN hostname or format can change in one place.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageBaseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_IMAGE_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://img.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;imageAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;imageBaseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a tiny board placeholder during startup. Do not automatically preload the full-resolution background. Preload raises priority, so a large background can delay JavaScript, fonts, or smaller assets that are required sooner.&lt;/p&gt;

&lt;p&gt;Only preload a resource after a Network trace shows that it is discovered too late and is genuinely required for the first playable frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Cache immutable assets with versioned URLs
&lt;/h2&gt;

&lt;p&gt;For local static images, Next.js can send a long cache lifetime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;minimumCacheTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31536000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:path*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.(png|jpg|jpeg|webp|avif|svg)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public, max-age=31536000, immutable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply equivalent cache rules at the CDN or object storage origin for assets served from another hostname. A header configured in Next.js does not control a separate CDN automatically.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;immutable&lt;/code&gt; is safe only when an asset change also changes its URL. Use content hashes, versioned filenames such as &lt;code&gt;tiles-atlas.v3.webp&lt;/code&gt;, or a version query controlled by the deployment. Never overwrite an immutable URL and expect returning players to receive the new file.&lt;/p&gt;

&lt;p&gt;If a critical asset uses a separate hostname, establish the connection early:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://img.example.com"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this for an origin needed near the top of the page, not for every third-party host.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Delay API work until the feature needs it
&lt;/h2&gt;

&lt;p&gt;A leaderboard request should not run while the renderer is initializing unless leaderboard data is visible on the first screen.&lt;/p&gt;

&lt;p&gt;Choose the trigger that matches the interface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch after the player starts the game.&lt;/li&gt;
&lt;li&gt;Fetch when the leaderboard approaches the viewport.&lt;/li&gt;
&lt;li&gt;Fetch after a run ends, when the result screen needs the ranking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a below-the-fold panel, &lt;code&gt;IntersectionObserver&lt;/code&gt; is usually enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;refreshRankings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rootMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;480px 0px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Disconnect the observer after the first request, and guard the fetch function against duplicate calls. The feature remains available, but its network and React work no longer competes with the first playable frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Consider inline CSS only after the large work is gone
&lt;/h2&gt;

&lt;p&gt;Once the JavaScript and media path is under control, a small game page may benefit from Next.js CSS inlining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;inlineCss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can remove a render-blocking stylesheet request and make the correctly sized game shell available with the HTML.&lt;/p&gt;

&lt;p&gt;It is a finishing optimization, not a first step. It increases the HTML size, repeats styles between pages, and removes the benefit of a separately cached stylesheet. Because the option is experimental, inspect the production output again after every Next.js upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Verify performance and gameplay together
&lt;/h2&gt;

&lt;p&gt;Every loading optimization changes timing, so a faster Lighthouse result is not enough. Run the production build and check that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The initial route does not download the game chunk before its intended trigger.&lt;/li&gt;
&lt;li&gt;Tutorial video and leaderboard requests do not start early.&lt;/li&gt;
&lt;li&gt;The game shell keeps stable dimensions while loading.&lt;/li&gt;
&lt;li&gt;Canvas pixels render instead of remaining blank or transparent.&lt;/li&gt;
&lt;li&gt;Mouse, keyboard, touch, undo, restart, and resize behavior still work.&lt;/li&gt;
&lt;li&gt;Texture URLs return &lt;code&gt;200&lt;/code&gt; and atlas frame mappings remain correct.&lt;/li&gt;
&lt;li&gt;Mobile and desktop layouts do not overflow or overlap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automate the repeatable checks with Playwright, then use Lighthouse for controlled performance comparisons. Change one related group at a time so a regression can be traced to a specific decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  A useful order of work
&lt;/h2&gt;

&lt;p&gt;For a Next.js browser game, this order usually gives the clearest return:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Separate the server-rendered page from the browser-only game runtime.&lt;/li&gt;
&lt;li&gt;Reduce the rendering-engine bundle.&lt;/li&gt;
&lt;li&gt;Delay tutorials, leaderboards, and below-the-fold content.&lt;/li&gt;
&lt;li&gt;Resize and compress page images and texture atlases.&lt;/li&gt;
&lt;li&gt;Add correct cache rules and connection hints.&lt;/li&gt;
&lt;li&gt;Test smaller finishing changes such as CSS inlining.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The core question is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Must this resource be downloaded, parsed, decoded, or executed before the player can make the first move?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is no, give it a later trigger. That is how a visually small game becomes a genuinely small initial load without removing features from the product.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webperf</category>
      <category>gamedev</category>
      <category>webgl</category>
    </item>
    <item>
      <title>Obvious Online Tools Worth Bookmarking in 2026</title>
      <dc:creator>scott winter</dc:creator>
      <pubDate>Tue, 23 Jun 2026 07:47:48 +0000</pubDate>
      <link>https://dev.to/scott_winter_77ced0700c92/seven-less-obvious-online-tools-worth-bookmarking-in-2026-578m</link>
      <guid>https://dev.to/scott_winter_77ced0700c92/seven-less-obvious-online-tools-worth-bookmarking-in-2026-578m</guid>
      <description>&lt;p&gt;Most tool lists repeat the same names: ChatGPT, Claude, Gemini, Perplexity, Canva, and a few other obvious picks. Those tools are useful, but they are not exactly discoveries anymore.&lt;/p&gt;

&lt;p&gt;This list is a little different. I wanted to collect practical, slightly less obvious tools that can help with writing, meetings, visual thinking, design assets, colors, UI drafts, and quick creative work. Some are AI-first products; some are simple browser tools that remove one small but annoying step from the day.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Napkin AI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.napkin.ai/" rel="noopener noreferrer"&gt;Napkin AI&lt;/a&gt; turns plain text into visual diagrams, flowcharts, and business-style graphics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paste notes, bullets, or a short explanation, then generate visuals from the text.&lt;/li&gt;
&lt;li&gt;Useful for blog posts, decks, internal docs, product explanations, and strategy notes.&lt;/li&gt;
&lt;li&gt;Better for communicating an idea visually than starting from a blank canvas.&lt;/li&gt;
&lt;li&gt;Handy when you need a diagram quickly but do not want to open a full design tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your written explanation feels too dense, Napkin AI can help turn it into something easier to scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Granola
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.granola.ai/" rel="noopener noreferrer"&gt;Granola&lt;/a&gt; is an AI meeting notepad for people who spend too much time in calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Captures meeting notes and turns them into cleaner summaries.&lt;/li&gt;
&lt;li&gt;Helps extract decisions, action items, and useful context after a conversation.&lt;/li&gt;
&lt;li&gt;Good for founders, product managers, recruiters, sales teams, and anyone doing back-to-back calls.&lt;/li&gt;
&lt;li&gt;Feels more like a private working notebook than a big meeting bot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One note: meeting tools can touch sensitive information, so check your sharing and privacy settings before using them for confidential calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Font Generator Online
&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://fontgeneratoronline.net/" rel="noopener noreferrer"&gt;Font Generator Online&lt;/a&gt; is a free fancy text generator for making stylized Unicode text you can copy and paste anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Includes 290+ font styles, such as cursive, gothic, bold, tiny, calligraphy, cute, bubble, 3D, Instagram, Discord, and more.&lt;/li&gt;
&lt;li&gt;Works directly in the browser: type your text, preview styles, then copy the result.&lt;/li&gt;
&lt;li&gt;Useful for social bios, usernames, captions, quick branding experiments, and profile decoration.&lt;/li&gt;
&lt;li&gt;No design software or account setup is needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a small tool, but it solves a common problem fast: making plain text look more distinctive.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Recraft
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.recraft.ai/" rel="noopener noreferrer"&gt;Recraft&lt;/a&gt; is an AI design platform for generating and editing images, vectors, icons, mockups, and brand assets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates images, illustrations, logos, icons, and vector-style assets.&lt;/li&gt;
&lt;li&gt;Useful when you need editable visual elements instead of just a flat AI image.&lt;/li&gt;
&lt;li&gt;Includes tools like background removal, upscaling, erasing, mockups, and image editing.&lt;/li&gt;
&lt;li&gt;A good fit for designers, indie makers, marketers, and product teams.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you often need small visual assets for landing pages, ads, or product mockups, Recraft is worth testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. DuckColor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://duckcolor.com/" rel="noopener noreferrer"&gt;DuckColor&lt;/a&gt; is an online AI color palette generator for websites, UI design, branding, and creative projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate palettes from a prompt when you know the mood but not the exact colors.&lt;/li&gt;
&lt;li&gt;Copy HEX values quickly for CSS, mockups, and design systems.&lt;/li&gt;
&lt;li&gt;Includes image color picking and a palette workspace for exploring combinations.&lt;/li&gt;
&lt;li&gt;Helps with contrast checks, which matters when colors need to be readable in real UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially useful for developers who can build the interface but do not want to guess their way through color choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Uizard
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uizard.io/" rel="noopener noreferrer"&gt;Uizard&lt;/a&gt; is an AI-assisted UI design tool for creating app, website, and desktop software mockups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Helps turn ideas, screenshots, or rough sketches into editable UI concepts.&lt;/li&gt;
&lt;li&gt;Useful for quick prototypes before opening heavier design tools.&lt;/li&gt;
&lt;li&gt;Good for product managers, founders, and developers who need to communicate a screen idea.&lt;/li&gt;
&lt;li&gt;Works well for early-stage layout exploration, not just polished final design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uizard is useful when the goal is to make an idea visible quickly enough for feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Krea
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.krea.ai/" rel="noopener noreferrer"&gt;Krea&lt;/a&gt; is a creative AI suite for generating, enhancing, and editing images, videos, and 3D assets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports image generation, video generation, enhancement, editing, and 3D-style workflows.&lt;/li&gt;
&lt;li&gt;Useful for experimenting with visual directions before committing to one style.&lt;/li&gt;
&lt;li&gt;Good for creators who want to iterate on visual concepts quickly.&lt;/li&gt;
&lt;li&gt;Works well for moodboards, campaign ideas, thumbnails, posters, and creative references.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you like exploring visual ideas through fast iteration, Krea gives you a broader playground than a single-purpose image generator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The best tools are often not the biggest ones. They are the ones that remove a specific point of friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Napkin AI helps turn text into visuals.&lt;/li&gt;
&lt;li&gt;Granola helps clean up meeting notes.&lt;/li&gt;
&lt;li&gt;Font Generator Online helps create stylized text quickly.&lt;/li&gt;
&lt;li&gt;Recraft helps generate and edit design assets.&lt;/li&gt;
&lt;li&gt;DuckColor helps choose better palettes.&lt;/li&gt;
&lt;li&gt;Uizard helps sketch UI ideas fast.&lt;/li&gt;
&lt;li&gt;Krea helps explore creative visual directions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Used together, they make a practical toolkit for creators, developers, indie makers, and small teams who want to move faster without adding too much process.&lt;/p&gt;

</description>
      <category>website</category>
      <category>software</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
