<?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: zerodays</title>
    <description>The latest articles on DEV Community by zerodays (@zerodays).</description>
    <link>https://dev.to/zerodays</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F8603%2F70b6113f-d2d4-4468-993c-ca9a42e965b0.png</url>
      <title>DEV Community: zerodays</title>
      <link>https://dev.to/zerodays</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zerodays"/>
    <language>en</language>
    <item>
      <title>From Idea to Reality in 2 Days: Bringing the Hackathon Approach into our Office</title>
      <dc:creator>Špela Buh</dc:creator>
      <pubDate>Wed, 19 Mar 2025 10:46:49 +0000</pubDate>
      <link>https://dev.to/zerodays/from-idea-to-reality-in-2-days-bringing-the-hackathon-approach-into-our-office-3g6k</link>
      <guid>https://dev.to/zerodays/from-idea-to-reality-in-2-days-bringing-the-hackathon-approach-into-our-office-3g6k</guid>
      <description>&lt;p&gt;If you read our &lt;a href="https://dev.to/zerodays/from-competitors-to-partners-our-7-year-journey-with-dragonhack-46fl"&gt;DragonHack blog&lt;/a&gt;, you already know what an important role the &lt;strong&gt;hackathon approach&lt;/strong&gt; has played in shaping our team dynamic and making us the team we are today. It threw us in headfirst, challenging us to solve real-life problems for the first time outside the classroom. And most importantly, it &lt;u&gt;gave us the opportunity to discover the best way to work&lt;/u&gt; while tackling really hard problems head-on and still creating something incredible within the time constraints.&lt;/p&gt;

&lt;p&gt;So, with a larger team than ever before and growing ambitions to explore some product ideas, we decided to see if this approach is still the right path for us, by &lt;u&gt;holding a two-day internal company hackathon&lt;/u&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Ground Rules
&lt;/h2&gt;

&lt;p&gt;Before we started, we had to set some ground rules for how the event and the work would take place.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sleep at home&lt;/strong&gt;: While true one-piece hackathons can be a lot of fun (staying up all night together, trying to get things done, and pushing through the slumps) we recognize that people have lives outside of work. So, we decided to spread the hackathon across two days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Think about the users and make sure you’re solving a problem&lt;/strong&gt;: It’s not just about the product; it’s about ensuring there is a product-user fit. Don’t just build something cool - make sure it actually solves a real-life problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make a demo and pitch the idea&lt;/strong&gt;: Think broader than just building it - show that you understand the problem and your product, and sell it to us.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  Let’s Start at the Beginning - What To Do?
&lt;/h2&gt;

&lt;p&gt;Of course, we couldn’t start without knowing what we wanted to work on. So, we kicked things off with a &lt;strong&gt;brainstorming session&lt;/strong&gt; a week before to define the problem we wanted to solve. Each team member had to &lt;u&gt;pitch two problems&lt;/u&gt; they were interested in tackling. The ideas were varied and diverse - ranging from development-focused issues that had bothered us for months to personal annoyances we encountered in everyday life and niche market problems we stumbled upon by chance.&lt;/p&gt;

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

&lt;p&gt;With over 20 different ideas, we needed a way to narrow them down. So, we did a bit of research and combined different resources to get the 13 questions we asked for each problem to evaluate them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is this a solution or a &lt;strong&gt;problem&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Are we &lt;strong&gt;the right team&lt;/strong&gt; to solve this problem?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market size&lt;/strong&gt; (large with little competition, or growing fast)?&lt;/li&gt;
&lt;li&gt;How &lt;strong&gt;big&lt;/strong&gt; is the problem?&lt;/li&gt;
&lt;li&gt;Can we imagine a good &lt;strong&gt;business model&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Can we reach the &lt;strong&gt;target audience&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Is there &lt;strong&gt;competition&lt;/strong&gt;? (Not necessarily bad - but do we need a new approach?)&lt;/li&gt;
&lt;li&gt;Would we &lt;strong&gt;use&lt;/strong&gt; this product ourselves?&lt;/li&gt;
&lt;li&gt;Has the solution only &lt;strong&gt;recently become possible&lt;/strong&gt; or necessary?&lt;/li&gt;
&lt;li&gt;Are there &lt;strong&gt;successful similar companies&lt;/strong&gt; in other markets?&lt;/li&gt;
&lt;li&gt;Can we see ourselves &lt;strong&gt;working&lt;/strong&gt; on this idea &lt;strong&gt;for years&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Is it &lt;strong&gt;scalable&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;What is the &lt;strong&gt;success rate&lt;/strong&gt; in this space?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the end of this process, we were left with four ideas that checked all the boxes. Three days later, after everyone had a bit of time to sleep on it, we voted and &lt;u&gt;selected the final two&lt;/u&gt;. With that, we were ready to start our hackathon.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Let’s Get Crackin’
&lt;/h2&gt;

&lt;p&gt;Time for the main event - let’s get to building. We started by setting clear expectations and splitting into two balanced teams. From there, we &lt;u&gt;defined key features, divided the work, and got to building&lt;/u&gt;.&lt;/p&gt;

&lt;p&gt;Each team was also tasked with completing a &lt;strong&gt;value proposition canvas&lt;/strong&gt; and creating a landing page to better understand the product, the problem, and how to communicate the value proposition. Then, the building began - of course, with a well-deserved pizza break along the way.&lt;/p&gt;

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

&lt;p&gt;The first day was all about tackling the toughest challenges first and ensuring there was something to show - because the next morning kicked off with a &lt;strong&gt;short demo&lt;/strong&gt; of what had been accomplished so far. The other team could ask questions and suggest ideas, giving us fresh momentum to push forward. But the hacking stopped at 15:30 sharp.&lt;/p&gt;

&lt;p&gt;Then, it was pens down and time to pitch. Both teams did an incredible job - both in building and presenting, resulting in &lt;strong&gt;two amazing demos&lt;/strong&gt;. But we’re not showing them to you just yet!&lt;/p&gt;

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

&lt;h2&gt;
  
  
  So Was This the Right Way?
&lt;/h2&gt;

&lt;p&gt;Ending our two-day journey with a celebratory beer, we would say, absolutely! Beyond just building something awesome, we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created a great environment for innovation, maintained full focus, and really pushed through it.&lt;/li&gt;
&lt;li&gt;Switched teams around a bit and explored different dynamics.&lt;/li&gt;
&lt;li&gt;Coordinated, explored new roles, and tried out different approaches.&lt;/li&gt;
&lt;li&gt;Thought more strategically, beyond just the technical aspects.&lt;/li&gt;
&lt;li&gt;Had an amazing team-building experience.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hackathon</category>
      <category>productdevelopment</category>
      <category>product</category>
      <category>teambuilding</category>
    </item>
    <item>
      <title>🧩 Beyond the Popup: Crafting Next-Level Chrome Extensions with CRXJS</title>
      <dc:creator>Tim Vučina</dc:creator>
      <pubDate>Wed, 19 Feb 2025 10:21:50 +0000</pubDate>
      <link>https://dev.to/zerodays/beyond-the-popup-crafting-next-level-chrome-extensions-with-crxjs-3elg</link>
      <guid>https://dev.to/zerodays/beyond-the-popup-crafting-next-level-chrome-extensions-with-crxjs-3elg</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;1. The Magic of Chrome Extensions&lt;/strong&gt; ✨
&lt;/h2&gt;

&lt;p&gt;Chrome extensions are more than just handy little tools - they’re like superpowers for your browser. From automating workflows to enhancing user experience, they can transform the way people interact with the web. But the real beauty? Extensions can seamlessly integrate into websites, modify content on the fly, and even leverage AI for mind-blowing results (like &lt;strong&gt;&lt;a href="https://www.classmate.study/" rel="noopener noreferrer"&gt;Classmate&lt;/a&gt;&lt;/strong&gt; does).  &lt;/p&gt;

&lt;p&gt;Instead of a dull definition, let’s highlight what makes extensions exciting:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They live inside your browser&lt;/strong&gt; – meaning instant access and control over webpages.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They hook into powerful APIs&lt;/strong&gt; – like the Chrome Storage API, Context Menus, Side Panels, and even WebRTC.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They enable automation, AI, and interaction enhancements&lt;/strong&gt; – basically, they let you mold the web to your will.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Choosing Your Stack: The Right Tools for the Job&lt;/strong&gt; 🛠️
&lt;/h2&gt;

&lt;p&gt;Unlike traditional web apps, building a Chrome extension usually requires a carefull balancing of performance, compatibility, and maintainability. There are multiple ways to approach extension development, from vanilla JavaScript to full-blown frameworks.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Popular Choices for Extension Development:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plain JavaScript&lt;/strong&gt; – Lightweight but messy for scaling.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React (or Preact)&lt;/strong&gt; – Great for UI-heavy extensions but requires careful bundling.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; – A must-have for maintainability.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite + ESBuild&lt;/strong&gt; – Super fast and developer-friendly for modern extensions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TailwindCSS&lt;/strong&gt; – Ideal for crafting polished UIs quickly.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase/Firebase&lt;/strong&gt; – Cloud storage, authentication, and real-time syncing made easy.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;strong&gt;Classmate&lt;/strong&gt;, we went with &lt;strong&gt;React, TypeScript, Vite, Tailwind, and Supabase&lt;/strong&gt;, ensuring a modern, scalable architecture with minimal bloat.  &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. The Unique Challenges &amp;amp; Superpowers of Extension Development&lt;/strong&gt; 🔥
&lt;/h2&gt;

&lt;p&gt;Chrome extensions offer &lt;strong&gt;incredible browser APIs&lt;/strong&gt; but also introduce quirks and gotchas that developers need to navigate.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Good: Browser APIs That Supercharge Your Extension&lt;/strong&gt; 🚀
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;chrome.tabs&lt;/strong&gt; – Interact with and modify webpages dynamically.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chrome.scripting&lt;/strong&gt; – Inject JavaScript and CSS into any page.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chrome.contextMenus&lt;/strong&gt; – Create custom right-click options for quick actions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chrome.storage&lt;/strong&gt; – Persist user settings without a backend.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chrome.identity&lt;/strong&gt; – Handle authentication seamlessly.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Bad: Common Pitfalls &amp;amp; Limitations&lt;/strong&gt; 😬
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manifest V2 → V3 transition&lt;/strong&gt; – Service workers replace background scripts, breaking some old workflows.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restricted access&lt;/strong&gt; – Some APIs are off-limits (e.g., network requests need special permissions).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No direct database storage&lt;/strong&gt; – Requires external storage like Firebase or Supabase.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance concerns&lt;/strong&gt; – Extensions should be lightweight to avoid slowing down the browser.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Ugly: Bad Practices That Can Doom Your Extension&lt;/strong&gt; ❌
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Over-permissioning&lt;/strong&gt; – Requesting too many permissions leads to rejection by the Chrome Web Store.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not optimizing storage&lt;/strong&gt; – Extensions with large local storage can slow down performance.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocking the main thread&lt;/strong&gt; – Keep content scripts efficient to avoid laggy pages.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Bridging the Gap: Messaging Between Popup &amp;amp; Content Scripts&lt;/strong&gt; 🔄
&lt;/h3&gt;

&lt;p&gt;One of the biggest challenges in Chrome extension development is getting different parts of the extension – popup, background scripts, and content scripts – to talk to each other efficiently. Unlike a traditional web app, where everything runs in a single execution context, Chrome extensions are split into &lt;strong&gt;isolated environments&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;This means:&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;The popup doesn’t directly control the webpage&lt;/strong&gt; – it must send messages.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;The content script can interact with the page but lacks access to extension APIs&lt;/strong&gt; – it needs to communicate with the background.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;The background service worker acts as a central hub&lt;/strong&gt;, receiving messages and dispatching commands.  &lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;How We Handle Messaging in Classmate&lt;/strong&gt; ⚡
&lt;/h4&gt;

&lt;p&gt;To keep our messaging system &lt;strong&gt;structured and scalable&lt;/strong&gt;, we use &lt;strong&gt;typed messages&lt;/strong&gt; with TypeScript, ensuring every communication has a clear format.  &lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Message Structure &amp;amp; Commands&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We define a set of commands, like:&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;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;START_SCREENSHOT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ASK_AI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TRIGGER_AUTOSOLVE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OPEN_SIDEBAR&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;Each message follows a strict format, using interfaces like:&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;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AskAI&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;BaseMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ASK_AI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text&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="nl"&gt;autoSolveData&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;AutoSolveQuestion&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures every part of our extension knows &lt;strong&gt;exactly&lt;/strong&gt; what kind of messages to expect, reducing errors.  &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Sending Messages to the Active Tab&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We use a helper function to &lt;strong&gt;find and message the active tab&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendMessageToActiveTab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Message&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;tab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getActiveTab&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;tab&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No active tab found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendMessageToTab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&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 makes sure we’re always targeting the &lt;strong&gt;currently open webpage&lt;/strong&gt;, whether it’s to &lt;strong&gt;trigger AI processing&lt;/strong&gt; or &lt;strong&gt;capture screenshots&lt;/strong&gt;.  &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Listening for Messages&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We use React hooks to set up &lt;strong&gt;message listeners&lt;/strong&gt; inside components. This makes handling commands like &lt;code&gt;"START_SCREENSHOT"&lt;/code&gt; &lt;strong&gt;declarative and efficient&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useMessageListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;START_SCREENSHOT&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;area-picker&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;This way, the UI updates reactively whenever the popup or background script sends a command.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Result? A Seamless Experience&lt;/strong&gt; 🎯
&lt;/h3&gt;

&lt;p&gt;With &lt;strong&gt;typed messages, structured commands, and well-defined listeners&lt;/strong&gt;, our extension avoids race conditions, unexpected behavior, and debugging nightmares. Instead, &lt;strong&gt;every part of the extension stays in sync&lt;/strong&gt;, making interactions smooth and &lt;strong&gt;instantaneous&lt;/strong&gt;. 🚀  &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;5. CRXJS: The Modern Way to Build Chrome Extensions&lt;/strong&gt; ⚡
&lt;/h2&gt;

&lt;p&gt;If you’re building Chrome extensions with modern web frameworks, CRXJS is a game-changer. It simplifies the development process by integrating seamlessly with Vite, allowing you to use React (or other frameworks) with minimal hassle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why CRXJS?
&lt;/h3&gt;

&lt;p&gt;Traditional Chrome extension development often involves complex configurations, dealing with manifest quirks, and managing different extension environments (background, content scripts, popup). CRXJS abstracts much of this complexity while staying lightweight and performant.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Zero-config setup&lt;/strong&gt; – Works out of the box with Vite.&lt;br&gt;
✅ &lt;strong&gt;Automatic bundling&lt;/strong&gt; – No need to manually manage content scripts and background scripts.&lt;br&gt;
✅ &lt;strong&gt;HMR (Hot Module Replacement) for popups &amp;amp; options pages&lt;/strong&gt; – Develop faster without needing to reload the extension manually.&lt;br&gt;
✅ &lt;strong&gt;TypeScript &amp;amp; React support&lt;/strong&gt; – Makes modern development workflows much easier.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting Up CRXJS in Your Project 🚀
&lt;/h3&gt;

&lt;p&gt;To get started, install CRXJS Vite plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;npm install @crxjs/vite-plugin -D
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, configure it in &lt;code&gt;vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react&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;crx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@crxjs/vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./manifest.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;crx&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;manifest&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 setup ensures that your extension files are properly processed, including background scripts, content scripts, and popup UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Efficiency and Simplicity 🏆
&lt;/h3&gt;

&lt;p&gt;With CRXJS, we eliminated the boilerplate, improved the development speed with HMR, and kept our extension lean and maintainable. It’s the perfect tool for modern Chrome extension development, allowing us to focus on building features instead of wrestling with configuration files.&lt;/p&gt;

&lt;p&gt;If you're working with React, Vite, or TypeScript, CRXJS is a no-brainer! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;6. Our Journey: How We Built Classmate&lt;/strong&gt; 🏆
&lt;/h2&gt;

&lt;p&gt;When developing &lt;strong&gt;Classmate&lt;/strong&gt; - AI-powered quiz assistant, we faced all the classic extension challenges:&lt;br&gt;&lt;br&gt;
✅ Ensuring a seamless user experience with &lt;strong&gt;React + Vite&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
✅ Handling authentication with &lt;strong&gt;Supabase&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
✅ Managing API calls efficiently with &lt;strong&gt;Zodios&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
✅ Avoiding Chrome Web Store rejection by keeping &lt;strong&gt;permissions minimal&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Through smart architecture choices and the right stack, we turned a simple idea into a powerful AI assistant for online learners. 🚀  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt; 💡&lt;br&gt;&lt;br&gt;
Chrome extension development is an exciting playground where you can experiment with browser APIs, automation, and AI. Whether you’re building productivity tools, AI-driven helpers, or something entirely unique, there’s no limit to what you can create.  &lt;/p&gt;

&lt;p&gt;Got an idea? Time to &lt;strong&gt;ship it!&lt;/strong&gt; 🚢💻  &lt;/p&gt;

</description>
      <category>chromeextension</category>
      <category>extensions</category>
      <category>crxjs</category>
      <category>aiextension</category>
    </item>
    <item>
      <title>From Competitors to Partners – Our 7-Year Journey with DragonHack</title>
      <dc:creator>Miha Majetič</dc:creator>
      <pubDate>Thu, 13 Feb 2025 11:51:15 +0000</pubDate>
      <link>https://dev.to/zerodays/from-competitors-to-partners-our-7-year-journey-with-dragonhack-46fl</link>
      <guid>https://dev.to/zerodays/from-competitors-to-partners-our-7-year-journey-with-dragonhack-46fl</guid>
      <description>&lt;p&gt;&lt;a href="https://dragonhack.si/" rel="noopener noreferrer"&gt;DragonHack&lt;/a&gt; is the longest-running and largest student hackathon, and it challenges over &lt;strong&gt;150&lt;/strong&gt; students each year to compete in a &lt;strong&gt;24-hour problem-solving marathon&lt;/strong&gt;. Last year, the event celebrated its 10th anniversary, and we are proud to have been part of this incredible journey for the past &lt;strong&gt;seven years&lt;/strong&gt;. Looking ahead, we hope our involvement continues for at least twice as long.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  24 Hours to Build a Team
&lt;/h2&gt;

&lt;p&gt;Our journey with DragonHack didn’t begin as company partners - we started on the other side, as competitors. For several years, &lt;strong&gt;our founding team participated in the hackathon&lt;/strong&gt;, experiencing firsthand the adrenaline, creativity, and teamwork that define the event.&lt;/p&gt;

&lt;p&gt;Each year, we participated in the 24-hour challenge, fueled by energy drinks, coffee and sugar, learning how to tackle projects, solve key problems, and create something presentable for the judging panel. What made it truly unique was how different it was from working on regular college assignments. In a classroom setting, you collaborate within structured guidelines, but at DragonHack, you're thrown into a real challenge with no predefined solutions. It’s an intense, fast-paced environment where you have to &lt;strong&gt;test yourself as a team&lt;/strong&gt;, experience both highs and lows together, and &lt;strong&gt;adapt quickly&lt;/strong&gt;. The atmosphere was electric - a non-stop cycle of brainstorming, problem-solving, and pushing through moments of stress and fatigue. It wasn’t just about completing tasks; it was about taking an idea, developing it under real-world constraints, and presenting it with confidence.&lt;/p&gt;

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

&lt;p&gt;Through these intense hours, we connected as a team, strengthened our problem-solving skills, and built resilience - &lt;u&gt;experiences that proved invaluable when we launched our own company.&lt;/u&gt; But above all, it was an absolute blast.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Continuing the Journey as Partners and Mentors
&lt;/h2&gt;

&lt;p&gt;When we founded &lt;a href="https://www.zerodays.dev/en" rel="noopener noreferrer"&gt;zerodays&lt;/a&gt;, it was a no-brainer to continue our involvement with DragonHack - this time as partners. However, partnership for us wasn’t just about brand visibility or the potential for finding new developers; it was, first and foremost, about &lt;strong&gt;mentorship&lt;/strong&gt;. Having benefitted greatly from the guidance of industry professionals during our time as competitors, we &lt;u&gt;wanted to pass that on to the next generation of hackers&lt;/u&gt;.&lt;/p&gt;

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

&lt;p&gt;Every year, at least two members of our team stay with the participants throughout the night, offering advice, technical insights, and support. We see this as an &lt;strong&gt;investment&lt;/strong&gt; not only in young, promising talent but &lt;strong&gt;in the broader tech community&lt;/strong&gt; as well.&lt;/p&gt;

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

&lt;p&gt;Beyond mentorship, DragonHack also brings value to our team and business. It offers &lt;strong&gt;long-term brand-building opportunities&lt;/strong&gt;, the chance to meet potential business partners, and a platform to exchange ideas with other industry experts. In fact, one of the ideas we originally pitched as competitors even evolved into a business venture.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Than Just an Event
&lt;/h2&gt;

&lt;p&gt;At its core, our continued involvement with DragonHack isn’t just about giving back — it’s about staying connected, sharing knowledge, and, most importantly, having fun.&lt;/p&gt;

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

&lt;p&gt;&lt;u&gt;We are incredibly grateful for the opportunities, experiences, and lessons this event has given us.&lt;/u&gt; It has shaped us as individuals and as a company, and we firmly believe it’s an invaluable experience for any young team looking to test their skills, discover how they collaborate under pressure, and explore new possibilities.&lt;/p&gt;

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

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

&lt;p&gt;&lt;em&gt;Here’s to many more years of DragonHack and to the next generation of innovators, creators, and problem-solvers who will take on the challenge.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>dragonhack</category>
      <category>mentorship</category>
      <category>teamwork</category>
    </item>
    <item>
      <title>🚀 Building an Interactive 3D Rocket Easter Egg with React Three Fiber</title>
      <dc:creator>Tim Vučina</dc:creator>
      <pubDate>Tue, 17 Sep 2024 09:16:23 +0000</pubDate>
      <link>https://dev.to/zerodays/building-an-interactive-3d-rocket-easter-egg-with-react-three-fiber-4pc</link>
      <guid>https://dev.to/zerodays/building-an-interactive-3d-rocket-easter-egg-with-react-three-fiber-4pc</guid>
      <description>&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMGkwcjhnejhkbmN4Y3AxdTNueXNoNHhiZ2IzZDJoZHZxYzV0bndjaCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/pVgDqx73n1ECZL5O4X/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMGkwcjhnejhkbmN4Y3AxdTNueXNoNHhiZ2IzZDJoZHZxYzV0bndjaCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/pVgDqx73n1ECZL5O4X/giphy.gif" width="480" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we hit 1000 followers on LinkedIn, we knew we had to celebrate in style! Inspired by Vercel's awesome post on &lt;a href="https://vercel.com/blog/building-an-interactive-3d-event-badge-with-react-three-fiber" rel="noopener noreferrer"&gt;Building an Interactive 3D Event Badge with React Three Fiber&lt;/a&gt;, we wanted to take that vibe and launch something fun of our own. So, what better way to celebrate than with a 3D rocket Easter egg on our shiny new website, &lt;strong&gt;&lt;a href="https://zerodays.dev" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt;&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;It’s one of those things that’s just &lt;strong&gt;fun to build&lt;/strong&gt;, like the digital equivalent of a fidget spinner, but way cooler. We had a blast working on it, and today we’re going to take you behind the scenes.&lt;/p&gt;

&lt;p&gt;Let's get straight into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Table of Contents:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;🏁 The Setup&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🛠️ Tools to Build Our 3D Rocket&lt;/li&gt;
&lt;li&gt;🎬 Setting Up the 3D Scene&lt;/li&gt;
&lt;li&gt;🎥 Camera Choice: Why We Went with Orthographic&lt;/li&gt;
&lt;li&gt;💡 Quick Tip: Handling Events with &lt;code&gt;eventSource&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🚀 The Main Actor: Our Interactive Rocket&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key Features&lt;/li&gt;
&lt;li&gt;Rocket Following Movement: Keeping Track of the Mouse&lt;/li&gt;
&lt;li&gt;💡 Quick Tip: Handling Events on &lt;code&gt;&amp;lt;group&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;🚀 Summary - Rocket&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;💥 Particle System: Fueling the Rocket’s Exhaust&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key Concepts&lt;/li&gt;
&lt;li&gt;💥 Summary - Particles&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🪨 SpaceRocks: Asteroids with Physics and Splitting&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key Features&lt;/li&gt;
&lt;li&gt;🪨 Summary - SpaceRocks&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🌌 SpaceEnvironment: Immersive Space Backdrop&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key Features&lt;/li&gt;
&lt;li&gt;💡 Quick Tip: Using &lt;code&gt;window.devicePixelRatio&lt;/code&gt; for Consistent Shader Rendering&lt;/li&gt;
&lt;li&gt;🌌 Summary - SpaceEnvironment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🏎️ Performance Boosts: Optimizing for Smooth Rocket Experience&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚀 1. Frame Loop Control: "Always" vs. "Demand"&lt;/li&gt;
&lt;li&gt;🚀 2. Custom &lt;code&gt;useViewportSize&lt;/code&gt; Hook&lt;/li&gt;
&lt;li&gt;🚀 3. Mesh Instancing for Planets&lt;/li&gt;
&lt;li&gt;🚀 4. Using Refs for Non-Rerender Values&lt;/li&gt;
&lt;li&gt;🚀 5. General React-Three-Fiber Performance Tips&lt;/li&gt;
&lt;li&gt;💡 Quick Tip: Canvas Size Limits on Some Devices&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🚀 Wrapping it Up: The Final Countdown&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🏁 The Setup:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🛠️ Tools to Build Our 3D Rocket
&lt;/h3&gt;

&lt;p&gt;We kept our tech stack lean but powerful to make this Easter egg &lt;em&gt;fly&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt;&lt;/strong&gt;: The core of our interactive UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;&lt;/strong&gt;: Strong typing to keep our code bug-free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/pmndrs/react-three-fiber" rel="noopener noreferrer"&gt;react-three-fiber&lt;/a&gt;&lt;/strong&gt;: The magic behind our 3D scene, integrating &lt;strong&gt;&lt;a href="https://threejs.org/" rel="noopener noreferrer"&gt;three.js&lt;/a&gt;&lt;/strong&gt; with React.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/pmndrs/drei" rel="noopener noreferrer"&gt;react-three-drei&lt;/a&gt;&lt;/strong&gt;: Handy helpers for cameras, lighting, and effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/pmndrs/react-three-rapier" rel="noopener noreferrer"&gt;react-three-rapier&lt;/a&gt;&lt;/strong&gt;: Physics engine for realistic collisions and rocket interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎬 Setting Up the 3D Scene
&lt;/h3&gt;

&lt;p&gt;The first step was setting up the basic 3D scene for our rocket. We used &lt;strong&gt;react-three-fiber&lt;/strong&gt; to handle rendering the scene inside a React component, along with &lt;strong&gt;react-three-drei&lt;/strong&gt; for some helpers like the camera and lighting. We also integrated &lt;strong&gt;react-three-rapier&lt;/strong&gt; for physics and collision detection.&lt;/p&gt;

&lt;p&gt;Here’s a simplified version of the initial setup:&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="s1"&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;Canvas&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-three/fiber&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;OrthographicCamera&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-three/drei&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;Physics&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-three/rapier&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Rocket&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/three/rocket&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;SpaceEnvironment&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/three/space-environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;SpaceRocks&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/three/space-rocks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RocketScene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrthographicCamera&lt;/span&gt; &lt;span class="na"&gt;makeDefault&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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="mi"&gt;0&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="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ambientLight&lt;/span&gt; &lt;span class="na"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;directionalLight&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&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="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Physics&lt;/span&gt; &lt;span class="na"&gt;gravity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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="mi"&gt;0&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="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Rocket&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SpaceRocks&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Physics&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SpaceEnvironment&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Canvas&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;RocketScene&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets up our 3D world with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rocket&lt;/strong&gt;: The star of the show.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SpaceEnvironment&lt;/strong&gt;: A cool space backdrop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SpaceRocks&lt;/strong&gt;: Floating space debris that adds extra flair—and danger! The rocket can collide with these rocks, causing them to split apart for a more dynamic and interactive experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lighting&lt;/strong&gt;: Ambient and directional lights for a cosmic glow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physics&lt;/strong&gt;: Handles collisions and interactions between objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎥 Camera Choice: Why We Went with Orthographic
&lt;/h3&gt;

&lt;p&gt;We chose an &lt;strong&gt;Orthographic Camera&lt;/strong&gt; for our scene because it keeps everything proportional, no matter where the rocket is flying. Since our rocket scene spans the entire page (with height dictated by the scrollable content), an orthographic camera lets us fly the rocket up and down smoothly without any weird perspective distortions. It ensures a consistent, user-friendly experience as you scroll through the page and interact with the rocket.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Quick Tip: Handling Events with &lt;a href="https://r3f.docs.pmnd.rs/api/canvas#:~:text=react%2Dthree/fiber%22-,eventSource,-The%20source%20where" rel="noopener noreferrer"&gt;&lt;code&gt;eventSource&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;To ensure the 3D rocket scene can still respond to pointer events (e.g., mouse or touch interactions) even when other page content overlays it, we used the &lt;code&gt;eventSource&lt;/code&gt; prop in our &lt;strong&gt;Canvas&lt;/strong&gt; component. This tells &lt;strong&gt;react-three-fiber&lt;/strong&gt; where to listen for events. By setting &lt;code&gt;eventSource&lt;/code&gt; to the root element (&lt;code&gt;#root&lt;/code&gt;), the rocket can receive events while other content layers above it.&lt;/p&gt;

&lt;p&gt;Here's how we set it up:&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;Canvas&lt;/span&gt;
  &lt;span class="na"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// Specifies the DOM element to listen for pointer events&lt;/span&gt;
  &lt;span class="na"&gt;eventPrefix&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;         &lt;span class="c1"&gt;// The event prefix that is cast into canvas pointer x/y events&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🚀 The Main Actor: Our Interactive Rocket
&lt;/h2&gt;

&lt;p&gt;The rocket is the star of the show—interacting with the environment, responding to mouse movements, and launching across the page. To make it truly dynamic, we used &lt;strong&gt;react-three-fiber&lt;/strong&gt; for rendering and &lt;strong&gt;react-three-rapier&lt;/strong&gt; for physics. Let’s break down the core functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State Machine&lt;/strong&gt;: Handles the rocket’s different states—idle, launching, following (tracking the mouse), and resetting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collision and Physics&lt;/strong&gt;: Using &lt;strong&gt;CapsuleCollider&lt;/strong&gt; and &lt;strong&gt;RoundConeCollider&lt;/strong&gt;, the rocket can collide with space rocks. Physics also helps simulate rocket movement and impulse on launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hover and Click Interaction&lt;/strong&gt;: The rocket responds to mouse hover with a scale-up effect, and clicking either launches it or resets its position.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth Movement&lt;/strong&gt;: The rocket tracks the mouse, using vectors to calculate distance and apply force, while also smoothly rotating to follow its flight direction in &lt;a href="https://r3f.docs.pmnd.rs/api/hooks#useframe" rel="noopener noreferrer"&gt;&lt;strong&gt;useFrame()&lt;/strong&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Particles&lt;/strong&gt;: Adds dynamic exhaust particles during the rocket’s movement.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Here’s a key snippet:
&lt;/h4&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="nx"&gt;Rocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Refs and Hooks&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rocketHovered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&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="c1"&gt;// State machine&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rocketState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transitionTo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStateMachine&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;states&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="na"&gt;launching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onEnter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;transitionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;following&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;500&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;following&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="na"&gt;resetting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onEnter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;resetRocket&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nf"&gt;transitionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&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="c1"&gt;// Rocket reset logic&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resetRocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&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="p"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;// Reset rocket position, velocity, rotation&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// Frame logic (executed every frame)&lt;/span&gt;
    &lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delta&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="p"&gt;...&lt;/span&gt;
      &lt;span class="c1"&gt;// Scale the rocket when hovered&lt;/span&gt;
      &lt;span class="nx"&gt;rocketHovered&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="nx"&gt;meshRef&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="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;meshRef&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="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&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="mi"&gt;1&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="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Execute state-specific logic&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rocketState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;resetRocket&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;launching&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyImpulse&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;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;following&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="c1"&gt;// Move rocket towards pointer, apply rotation&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;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RigidBody&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;rigidBodyRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CapsuleCollider&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RoundConeCollider&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;group&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;meshRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;onPointerEnter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rocketHovered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&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="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;onPointerLeave&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rocketHovered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&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="si"&gt;}&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;rocketState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;following&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;transitionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resetting&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="nf"&gt;transitionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;launching&lt;/span&gt;&lt;span class="dl"&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;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RocketModel&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;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RigidBody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Our own particle effects component of the rocket exhaust clouds */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Particles&lt;/span&gt;
          &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rocketState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;launching&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;rocketState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;following&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;objectRef&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;exhaustMeshRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;group&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;h4&gt;
  
  
  Rocket Following Movement: Keeping Track of the Mouse
&lt;/h4&gt;

&lt;p&gt;One of the coolest parts of our rocket is its ability to follow the mouse as it flies through space. This is powered by the &lt;code&gt;useFrame&lt;/code&gt; hook in &lt;strong&gt;react-three-fiber&lt;/strong&gt;, which lets us update the rocket's position and rotation every frame. Here's how we handle the rocket's smooth movement and rotation while tracking the user's pointer.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Points:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mouse Tracking&lt;/strong&gt;: We convert the normalized device coordinates (NDC) from the pointer into viewport coordinates and move the rocket towards that point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distance Calculation&lt;/strong&gt;: We compute the distance between the rocket's current position and the target, using that to apply a force in the correct direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotation&lt;/strong&gt;: The rocket smoothly rotates to face the direction it’s moving, giving a realistic flight experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Here’s a simplified version of how we achieve this:
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delta&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;rocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rigidBodyRef&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;rocket&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&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="c1"&gt;// Convert pointer coordinates to viewport space&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&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;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Calculate distance between current rocket position and target&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Vector3&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;targetPos&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;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&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;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distanceTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Apply impulse to move rocket toward target&lt;/span&gt;
  &lt;span class="nx"&gt;direction&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="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetPos&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPos&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;multiplyScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyImpulse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;direction&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Calculate the rotation angle and smoothly rotate the rocket&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rotationAngle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;currentPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;currentPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;targetQuaternion&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="nf"&gt;setFromAxisAngle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotationAxis&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="nx"&gt;rotationAngle&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;slerpedQuaternion&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="nf"&gt;slerpQuaternions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;targetQuaternion&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="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slerpedQuaternion&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="kc"&gt;true&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;h3&gt;
  
  
  💡 Quick Tip: Handling Events on &lt;code&gt;&amp;lt;group&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When dealing with &lt;strong&gt;three.js&lt;/strong&gt; and raycasting, it's common to have multiple meshes or submeshes within a &lt;code&gt;&amp;lt;group&amp;gt;&lt;/code&gt;. This can result in multiple &lt;code&gt;onClick&lt;/code&gt; or &lt;code&gt;onPointerEnter&lt;/code&gt; events being fired as each submesh gets hit by the raycaster. To avoid this, we can use &lt;code&gt;e.stopPropagation()&lt;/code&gt; to prevent multiple event triggers.&lt;/p&gt;

&lt;p&gt;Here’s a quick example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rocket"&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Prevent multiple onClick calls&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;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RocketModel&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;pointLight&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;mesh&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚀 Summary - Rocket:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;State Machine&lt;/strong&gt;: Controls the rocket's different modes—&lt;strong&gt;idle&lt;/strong&gt;, &lt;strong&gt;launching&lt;/strong&gt;, &lt;strong&gt;following&lt;/strong&gt; (tracking the mouse), and &lt;strong&gt;resetting&lt;/strong&gt;. This ensures smooth transitions between states and interactions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Physics and Collisions&lt;/strong&gt;: Using &lt;strong&gt;CapsuleCollider&lt;/strong&gt; and &lt;strong&gt;RoundConeCollider&lt;/strong&gt;, the rocket can collide with objects like space rocks. Physics-based movement and impulse handling ensure realistic responses to collisions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mouse Interaction&lt;/strong&gt;: The rocket grows when hovered over, thanks to scaling effects. Clicking it launches the rocket or resets it to its initial state, adding fun interaction for users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Smooth Mouse Tracking&lt;/strong&gt;: The rocket tracks the mouse position smoothly, with calculated forces applied based on the distance to the pointer. It rotates toward the direction it’s moving, giving the feeling of true flight.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Particles&lt;/strong&gt;: The dynamic exhaust particles follow the rocket, adding visual flair as it launches and moves.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💥 Particle System: Fueling the Rocket’s Exhaust
&lt;/h2&gt;

&lt;p&gt;Our rocket wouldn't feel complete without some epic exhaust particles! We built a custom particle system using &lt;strong&gt;three.js&lt;/strong&gt; for rendering and shader-based control to give it that dynamic look. The system is fairly customizable, allowing us to control everything from particle lifetime to velocity and turbulence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYWk2MHhucmh6Njg3amFsN2dlcjVxbGkwenp6eGd3aHJpeXFod21reSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/bkXqlyUk9KEV2QPEya/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYWk2MHhucmh6Njg3amFsN2dlcjVxbGkwenp6eGd3aHJpeXFod21reSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/bkXqlyUk9KEV2QPEya/giphy.gif" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Concepts:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Custom Shader Material&lt;/strong&gt;: We use a &lt;strong&gt;ShaderMaterial&lt;/strong&gt; to control particle appearance, such as size and color. The fragment shader includes logic to fade particles based on their age.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ParticleShaderMaterial&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;ShaderMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cyan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;pointSize&lt;/span&gt;&lt;span class="p"&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="mf"&gt;1.0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;dpr&lt;/span&gt;&lt;span class="p"&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;vertexShader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    attribute float age;
    varying float vAge;
    void main() {
      vAge = age;
      gl_PointSize = ...;
      gl_Position = ...;
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fragmentShader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    varying float vAge;
    void main() {
      float alpha = 1.0 - vAge; // Fade based on age
      gl_FragColor = vec4(color, alpha);
    }
  `&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;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Particle Initialization&lt;/strong&gt;: Particles are initialized with random positions and velocities, creating a realistic spread for the exhaust. Each particle has attributes like position, age, and size.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initializeParticles&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;velocities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;ages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;sizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;positions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt; &lt;span class="c1"&gt;// Set initial position&lt;/span&gt;
    &lt;span class="nx"&gt;velocities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(...));&lt;/span&gt; &lt;span class="c1"&gt;// Random velocity&lt;/span&gt;
    &lt;span class="nx"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;emissionRate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Stagger particle emission&lt;/span&gt;
    &lt;span class="nx"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;sizeVariance&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&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="nx"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;velocities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sizes&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;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frame Updates&lt;/strong&gt;: Each frame, we update particle positions based on their velocity and apply gravity and turbulence. As particles age, they fade out and are reset when they reach their lifetime limit.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Update particle position and age every frame&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxParticles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;ages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;resetParticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Reset expired particles&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;updateParticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delta&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;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dynamic Particle Reset&lt;/strong&gt;: When a particle’s age reaches its limit, it’s reset to a new position, velocity, and age, making it ready for the next emission cycle.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resetParticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Reset particle to new random position and velocity&lt;/span&gt;
  &lt;span class="nx"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
  &lt;span class="nx"&gt;velocities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
  &lt;span class="nx"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&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;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Component Structure:
&lt;/h4&gt;

&lt;p&gt;The particle system is rendered as a &lt;code&gt;&amp;lt;points&amp;gt;&lt;/code&gt; mesh with buffer attributes for position, age, and size. A custom &lt;strong&gt;ShaderMaterial&lt;/strong&gt; controls particle appearance and behavior.&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;points&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="si"&gt;}&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;meshRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bufferGeometry&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"geometry"&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;bufferAttribute&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"attributes-position"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;positions&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bufferAttribute&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"attributes-age"&lt;/span&gt; &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;itemSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bufferAttribute&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"attributes-particleSize"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;sizes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;itemSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;bufferGeometry&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;primitive&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"material"&lt;/span&gt; &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ParticleShaderMaterial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;transparent&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;points&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  💥 Summary - Particles:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom Shader Material&lt;/strong&gt;: We use a &lt;strong&gt;ShaderMaterial&lt;/strong&gt; to manage particle appearance, handling size, color, and fade effects. The particles fade based on their age, providing a realistic exhaust effect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Particle Initialization&lt;/strong&gt;: Each particle is randomly initialized with a position, velocity, age, and size. This randomness gives the exhaust a natural spread as the rocket moves.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frame Updates&lt;/strong&gt;: Every frame, the system updates particle positions based on their velocity and applies effects like gravity and turbulence. As particles age, they fade out and are reset when their lifetime ends.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic Particle Reset&lt;/strong&gt;: When a particle expires, it’s reset with new random properties (position, velocity, etc.), ensuring continuous emission without needing to generate new particles from scratch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Efficient Structure&lt;/strong&gt;: The system leverages a &lt;strong&gt;&lt;code&gt;&amp;lt;points&amp;gt;&lt;/code&gt;&lt;/strong&gt; mesh with buffer attributes for efficient handling of particle data. The &lt;strong&gt;ShaderMaterial&lt;/strong&gt; manages the rendering and visual effects, keeping everything performant.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🪨 SpaceRocks: Asteroids with Physics and Splitting
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;SpaceRocks&lt;/strong&gt; component brings extra life to our scene by generating asteroid-like rocks that float around and can be split upon collisions. Using &lt;strong&gt;Rapier&lt;/strong&gt; for physics, these rocks bounce around, interact with the rocket, and split dynamically when hit hard enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNmM1cmVvcTkweGQzb2J5dG9yM3A2ZjQ2cHFvcTFkaWprYXRqOW12diZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Xw9qVN28AAyjHEyBBq/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNmM1cmVvcTkweGQzb2J5dG9yM3A2ZjQ2cHFvcTFkaWprYXRqOW12diZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Xw9qVN28AAyjHEyBBq/giphy.gif" width="480" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rock Geometry&lt;/strong&gt;: Each rock is created using a &lt;strong&gt;&lt;a href="https://threejs.org/docs/#examples/en/geometries/ConvexGeometry" rel="noopener noreferrer"&gt;ConvexGeometry&lt;/a&gt;&lt;/strong&gt; made from random vertices. This results in irregular, rock-like shapes.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateRockGeometry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;vertices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geometry&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;ConvexGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;geometry&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;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rock Splitting&lt;/strong&gt;: When a rock collides with enough force, it's split into two smaller rocks. The splitting is handled by calculating the intersection points along a defined plane, then generating two new rocks from the original.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;splitRock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rockGeometry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConvexGeometry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;verticesA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;verticesB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="c1"&gt;// Split the geometry along a plane&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plane&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;Plane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&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="mi"&gt;0&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="mi"&gt;0&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;geometryA&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;ConvexGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;verticesA&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;geometryB&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;ConvexGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;verticesB&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;geometryA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;geometryB&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;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Collision Handling&lt;/strong&gt;: When a rock collides with the rocket or another object, we compute the force of the collision. If the force is above a threshold, the rock splits.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;onContactForce&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;payload&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;forceMag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalForceMagnitude&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100000&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;forceMag&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;handleCollision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceVec&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Only split if the force is strong enough&lt;/span&gt;
&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Rock Generation:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Rocks are randomly positioned and given velocity in a grid. They are assigned attributes like velocity, angular velocity, and the ability to split on collisions.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rockId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idPrefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_rock_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;rockMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rockId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RapierRigidBody&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gridCells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;velocity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...),&lt;/span&gt;
    &lt;span class="na"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateRockGeometry&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;canSplit&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;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&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;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Component Structure:
&lt;/h4&gt;

&lt;p&gt;Each rock is a &lt;strong&gt;RigidBody&lt;/strong&gt; with properties such as restitution (bounciness) and friction. These rocks interact dynamically, bouncing off the environment and splitting upon impact.&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;RigidBody&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;rock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;linearVelocity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;velocity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;restitution&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Bouncy collisions&lt;/span&gt;
    &lt;span class="na"&gt;friction&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;onContactForce&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleCollision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceVec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Handle rock splitting&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Hull - Auto-generates mesh collider for convex geometries */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MeshCollider&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;"hull"&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;mesh&lt;/span&gt; &lt;span class="na"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;primitive&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"material"&lt;/span&gt; &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rockMaterial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;MeshCollider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RigidBody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🪨 Summary - SpaceRocks:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Geometries&lt;/strong&gt;: Rocks are randomly generated with irregular shapes using &lt;strong&gt;ConvexGeometry&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collisions and Splitting&lt;/strong&gt;: Rocks split into smaller rocks upon high-force collisions, creating dynamic interactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physics Integration&lt;/strong&gt;: Using &lt;strong&gt;Rapier&lt;/strong&gt; for realistic movement, friction, and bouncy collisions makes the rocks behave convincingly in the scene.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This system adds an extra layer of interactivity and fun as the rocket navigates through space!&lt;/p&gt;

&lt;h2&gt;
  
  
  🌌 SpaceEnvironment: Immersive Space Backdrop
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;SpaceEnvironment&lt;/strong&gt; component brings a dynamic backdrop to our rocket scene. It includes a starfield and a collection of planets scattered throughout space. Here’s how we built it using &lt;strong&gt;three.js&lt;/strong&gt;, &lt;strong&gt;react-three-fiber&lt;/strong&gt;, and shaders for custom effects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExd3NvMnI2cjg2MTl3bXdqbzRiZzE4eTl1aXNpeDNzemt0ZzV4cWhuMyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/GPdIgFzpid3iFDKD34/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExd3NvMnI2cjg2MTl3bXdqbzRiZzE4eTl1aXNpeDNzemt0ZzV4cWhuMyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/GPdIgFzpid3iFDKD34/giphy.gif" width="960" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dynamic Starfield&lt;/strong&gt;: Stars are randomly generated in a cylindrical volume (height = pageHeight) and made to twinkle with a custom shader that controls size and opacity. The stars appear to blink and fade, simulating a living, dynamic space scene.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;StarShaderMaterial&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;ShaderMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&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="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&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="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;dpr&lt;/span&gt;&lt;span class="p"&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="mf"&gt;1.0&lt;/span&gt; &lt;span class="p"&gt;}},&lt;/span&gt;
  &lt;span class="na"&gt;vertexShader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    varying float vTwinkle; 
    uniform float time;
    void main() {
      vTwinkle = 0.5 + 0.5 * sin(time + position.x * 10.0);
      gl_PointSize = ...;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fragmentShader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    varying float vTwinkle;
    void main() {
      gl_FragColor = vec4(color, opacity * vTwinkle);
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;transparent&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;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Instanced Planets&lt;/strong&gt;: We use instancing to efficiently render planets at random positions within the space. Each planet has a random color, size, and location, filling the scene with variety.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;planets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Instance&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;getRandomInCylinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;innerPadding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;planetColors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;planetColors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&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="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;innerPadding&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rotating Space Environment&lt;/strong&gt;: The entire space environment rotates slowly, making the stars and planets appear to move in the background.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;environmentRef&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="nx"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.015&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;StarShaderMaterial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElapsedTime&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;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  💡 Quick Tip: Using &lt;code&gt;window.devicePixelRatio&lt;/code&gt; for Consistent Shader Rendering
&lt;/h3&gt;

&lt;p&gt;When working with shaders that involve &lt;strong&gt;point size&lt;/strong&gt; (like stars in a starfield), it’s crucial to account for varying screen resolutions and pixel densities. By incorporating &lt;code&gt;window.devicePixelRatio&lt;/code&gt;, you ensure that your shader adjusts to different screens, maintaining consistent rendering across devices.&lt;/p&gt;

&lt;p&gt;Here's how we applied this:&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="nx"&gt;StarShaderMaterial&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;ShaderMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;dpr&lt;/span&gt;&lt;span class="p"&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// Ensures consistent point size across devices&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;vertexShader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    uniform float dpr;
    void main() {
      gl_PointSize = gl_PointSize * dpr; // Adjust point size by device pixel ratio
    }
  `&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;&lt;strong&gt;Reminder:&lt;/strong&gt; Always update the &lt;code&gt;dpr&lt;/code&gt; value in your &lt;code&gt;useFrame&lt;/code&gt; or relevant hook when screens change, like dragging your window across monitors with different resolutions!&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="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;StarShaderMaterial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&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;h4&gt;
  
  
  Component Structure:
&lt;/h4&gt;

&lt;p&gt;The stars and planets are rendered inside a &lt;code&gt;&amp;lt;group&amp;gt;&lt;/code&gt; element. Stars are rendered using &lt;code&gt;&amp;lt;points&amp;gt;&lt;/code&gt;, while planets are created using &lt;strong&gt;&lt;a href="https://drei.docs.pmnd.rs/performances/instances" rel="noopener noreferrer"&gt;Instances&lt;/a&gt;&lt;/strong&gt; for efficient rendering.&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;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Instanced stars */&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;points&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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="mi"&gt;0&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="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bufferGeometry&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;bufferAttribute&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"attributes-position"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;stars&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;bufferGeometry&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;primitive&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"material"&lt;/span&gt; &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;StarShaderMaterial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;points&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Instanced planets */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Instances&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;material&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;PlanetMaterial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;sphereGeometry&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&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;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;planets&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Instances&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;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🌌 Summary - SpaceEnvironment:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Starfield&lt;/strong&gt;: Twinkling stars scattered randomly in a cylindrical space add life to the environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instanced Planets&lt;/strong&gt;: Randomly positioned and scaled planets provide variety and depth to the scene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotating Environment&lt;/strong&gt;: Slow rotation of the entire backdrop makes space feel vast and alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth Transitions&lt;/strong&gt;: Stars and planets fade in and out seamlessly when the environment becomes visible or hidden.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This space environment creates an immersive, dynamic backdrop for the rocket's adventure, contributing to the overall feeling of motion and depth in the scene.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏎️ Performance Boosts: Optimizing for Smooth Rocket Experience
&lt;/h2&gt;

&lt;p&gt;For an interactive experience like our rocket scene, keeping performance top-notch is critical—especially when dealing with 3D rendering and physics calculations. Let’s explore how we squeezed out those extra frames and kept things smooth:&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 1. Frame Loop Control: "Always" vs. "Demand"
&lt;/h3&gt;

&lt;p&gt;To prevent unnecessary GPU usage when the rocket isn’t in view, we created a trigger element just below the content on the webpage. This switch toggles the &lt;strong&gt;frame loop&lt;/strong&gt; between &lt;code&gt;"always"&lt;/code&gt; and &lt;code&gt;"demand"&lt;/code&gt;, ensuring the browser only re-renders when needed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Always"&lt;/strong&gt;: Used when the rocket is visible (scrolled into view) or animating, ensuring buttery-smooth performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Demand"&lt;/strong&gt;: Used when the rocket is idle and not in view, meaning the scene only re-renders when a specific event occurs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This technique helps reduce GPU strain and optimizes battery usage for mobile and laptop users.&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;Canvas&lt;/span&gt; &lt;span class="na"&gt;frameloop&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isRocketVisibleOrFlying&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;always&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;demand&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;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚀 2. Custom &lt;code&gt;useViewportSize&lt;/code&gt; Hook
&lt;/h3&gt;

&lt;p&gt;We opted for a custom &lt;code&gt;useViewportSize()&lt;/code&gt; hook to track viewport changes without triggering excessive re-renders. While &lt;code&gt;useThree()&lt;/code&gt;'s &lt;code&gt;{ viewport/size }&lt;/code&gt; would cause rerenders on every scroll, we throttle viewport size checks to avoid performance hits.&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="nx"&gt;useViewportSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSize&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;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elapsedTime&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;throttleTime&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.01&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&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;size&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;size&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;h3&gt;
  
  
  🚀 3. Mesh Instancing for Planets
&lt;/h3&gt;

&lt;p&gt;For efficiency, we used &lt;strong&gt;mesh instancing&lt;/strong&gt; to handle the rendering of planets. Instancing allows you to render multiple copies of a geometry while reusing the same material, which drastically reduces the overhead of drawing each individual planet in the scene.&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;Instances&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;material&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;PlanetMaterial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;sphereGeometry&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&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;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;planets&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Instances&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚀 4. Using Refs for Non-Rerender Values
&lt;/h3&gt;

&lt;p&gt;When dealing with dynamic updates inside the &lt;code&gt;useFrame()&lt;/code&gt; loop, we stored frequently changing values (like positions or velocities) in &lt;strong&gt;Refs&lt;/strong&gt;. This way, we can manipulate them directly without triggering component rerenders, which saves CPU cycles.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 5. General React-Three-Fiber Performance Tips
&lt;/h3&gt;

&lt;p&gt;Lastly, we adhered to best practices from the &lt;strong&gt;React Three Fiber docs&lt;/strong&gt; to avoid performance pitfalls. Some of these include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Avoid excessive use of &lt;code&gt;useFrame()&lt;/code&gt;&lt;/strong&gt; unless absolutely necessary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch updates&lt;/strong&gt; by using &lt;code&gt;setState()&lt;/code&gt; sparingly inside animation loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimize new object creation&lt;/strong&gt; inside loops to prevent garbage collection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more advanced performance tips, be sure to check out the official &lt;a href="https://r3f.docs.pmnd.rs/advanced/pitfalls" rel="noopener noreferrer"&gt;React Three Fiber pitfalls guide&lt;/a&gt; and &lt;a href="https://r3f.docs.pmnd.rs/advanced/scaling-performance" rel="noopener noreferrer"&gt;performance optimization tips&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Quick Tip: Canvas Size Limits on Some Devices
&lt;/h3&gt;

&lt;p&gt;When working with large canvases, remember that some devices (like &lt;strong&gt;Android Chrome&lt;/strong&gt; and &lt;strong&gt;Firefox on Desktop&lt;/strong&gt;) have size limitations that may not render properly if the canvas height exceeds 4096px. Always account for these limitations and consider dynamic scaling or feature disabling for large viewports!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;browserName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Firefox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isDesktop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setSize&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="k"&gt;return&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;By implementing these performance tweaks, we ensured a smooth, immersive experience without bogging down users' devices—whether they’re on mobile or desktop. 🖥️📱&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Wrapping it Up: The Final Countdown
&lt;/h2&gt;

&lt;p&gt;Celebrating our &lt;strong&gt;1,000 followers on LinkedIn&lt;/strong&gt; with this interactive 3D rocket has been an absolute blast! From building a dynamic, mouse-controlled rocket to adding splitting space rocks, twinkling stars, and performance optimizations—this project has truly been a fun journey.&lt;/p&gt;

&lt;p&gt;It’s projects like these that remind us why we love what we do: blending creativity, tech, and a little bit of rocket science (okay, a lot of rocket science). We hope you enjoyed reading about how we put it all together and maybe even picked up a few tips for your own interactive web projects.&lt;/p&gt;

&lt;p&gt;Feel free to check out the live rocket Easter egg on &lt;strong&gt;&lt;a href="https://zerodays.dev" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt;&lt;/strong&gt; and, of course, keep an eye out for more interactive fun as we continue to build cool things and push the boundaries of what’s possible in web development.&lt;/p&gt;

&lt;p&gt;Here’s to the next milestone—and maybe, the next rocket! 🚀&lt;/p&gt;

&lt;p&gt;Thanks for reading and following along!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>tutorial</category>
      <category>threejs</category>
    </item>
    <item>
      <title>Should Startups Outsource Software Development?</title>
      <dc:creator>Miha Majetič</dc:creator>
      <pubDate>Fri, 30 Aug 2024 11:36:15 +0000</pubDate>
      <link>https://dev.to/zerodays/should-startups-outsource-software-development-4jhf</link>
      <guid>https://dev.to/zerodays/should-startups-outsource-software-development-4jhf</guid>
      <description>&lt;p&gt;&lt;em&gt;"Why would we hire an agency instead of simply employing developers that will work full-time for us?"&lt;/em&gt; … is a question we often hear when we pitch ourselves to startups. And it’s a completely valid question! Why would somebody want to pay an outsourced team, when it could be cheaper or easier to have somebody on the inside? But is this really the case?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who are we?&lt;/strong&gt;&lt;br&gt;
Firstly, let’s introduce ourselves - we are zerodays, a custom software development team based in Slovenia, with extensive experience in working with startups. They have been a part of our story right from the beginning of our company.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3tzyy8yr8wg1cse0pg3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3tzyy8yr8wg1cse0pg3.jpg" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some of the more notable startups we worked on since the early startup days were for example &lt;strong&gt;&lt;a href="https://astra.si/ai/" rel="noopener noreferrer"&gt;Astra AI&lt;/a&gt;&lt;/strong&gt; (AI powered math tutor), Proky.io (online platform that connected clients and horeca suppliers) and &lt;strong&gt;&lt;a href="https://llamajet.com/" rel="noopener noreferrer"&gt;LlamaJET&lt;/a&gt;&lt;/strong&gt; (pharmaceutical marker for laboratory labware).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should you outsource?&lt;/strong&gt;&lt;br&gt;
So do we believe that outsourcing is the right choice for all startups? Well, the answer differs a bit based on the founders - are they themselves a technical founder and do they already have a team or not, and of course based on the industry they operate in, but shortly - yes. Outsourcing the development team is beneficial in each case. We can only speak from experience, but in our work with startups, we've gained valuable insights that make us believe outsourcing to a partner is the best move for startups for multiple reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Worked Across Different Niches:&lt;/strong&gt; An outsourced agency has probably worked in various industries, which means they’ve seen a lot and can quickly adapt to different challenges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Learning from Mistakes and Gaining Knowledge:&lt;/strong&gt; Outsourced development partners witness the ups and downs of startup life while working with startups, so they know what works and what doesn’t. Additionally, by partnering with an established team, you gain access to extensive knowledge, including valuable resources like code templates that we have crafted for the best performance, which you would otherwise have to develop on your own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Efficient Resource Use:&lt;/strong&gt; Instead of hiring full-time staff for everything, an agency can cover all your needs with fewer people. For example, providing a third of the time for frontend, backend, and DevOps each month rather than having to hire three separate in-house full-time developers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Flexible Work Model:&lt;/strong&gt; The startup world is unpredictable, and when work slows down, you don’t have to worry about layoffs with an outsourced development partner. You can just scale back the work, pause it, or ramp it up as needed—whatever works best for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Agencies Stick Around:&lt;/strong&gt; Unlike in-house developers who might leave and take their knowledge with them, agencies should ensure that all the expertise stays with your project. Even if someone from the team moves on, the knowledge stays, so you don’t lose any momentum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Faster Turnaround:&lt;/strong&gt; Agencies usually already have an established team that can get to work quickly, saving you time and costs associated with building your own team from scratch. The shorter learning curve also allows to start projects swiftly, without the delays of assembling and onboarding a new team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Comprehensive Service and Quality:&lt;/strong&gt; Outsourced partners should focus on finding and training top developers, taking care of all employment aspects, so you can concentrate solely on your product. Finding and onboarding new developers is challenging, but with the right partner, you won’t have to build your dream team - they handle it all for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So should startups without tech founders or already established team outsource their software development?&lt;/strong&gt;&lt;br&gt;
That’s obviously a pretty clear-cut case—it’s easy to see why someone with limited tech knowledge would choose to trust their software development to an outsourced team. These teams bring in experts from different areas of software development, make smart tech decisions, and can even lead the project on their own. So, for all the reasons we’ve talked about, outsourcing is definitely the way to go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How about startups which have a tech founder/team?&lt;/strong&gt;&lt;br&gt;
If the startup already has a technical team or founders who take on the main development roles, an agency might not always be the perfect fit. However, there are still some advantages to outsourcing, mainly when it comes to the increase in the amount of work—instead of temporarily employing someone, an outsourced agency can easily cover the spikes for them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpuerzxeamrc0mvtum40.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpuerzxeamrc0mvtum40.jpg" alt="Image description" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All in all, we can say that outsourcing is the right path for many startups. From extensive experience, to faster turnaround the benefits of having a great development partner who is committed to delivering outstanding results is the key advantage in the volatile startup world. And let’s not forget the most important thing - the passion for work and creating amazing digital products. &lt;/p&gt;

&lt;p&gt;If you are looking for a top notch development partner with experience and passion, reach out to us, and &lt;a href="https://www.zerodays.dev/en/contact" rel="noopener noreferrer"&gt;let’s make something great together&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This blog was made by the awesome team at &lt;a href="https://zerodays.dev" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>softwaredevelopment</category>
      <category>outsourcing</category>
      <category>agency</category>
    </item>
    <item>
      <title>How to Translate Your Next.js App in 5 Minutes With Crowdin</title>
      <dc:creator>Žan Horvat</dc:creator>
      <pubDate>Wed, 14 Aug 2024 11:00:48 +0000</pubDate>
      <link>https://dev.to/zerodays/how-to-translate-your-nextjs-app-in-5-minutes-with-crowdin-1pe0</link>
      <guid>https://dev.to/zerodays/how-to-translate-your-nextjs-app-in-5-minutes-with-crowdin-1pe0</guid>
      <description>&lt;p&gt;For the majority of websites using content management systems such as Webflow or WordPress, localization is not an issue, as it can be relatively easily done through the system. &lt;/p&gt;

&lt;p&gt;But for &lt;strong&gt;custom solutions&lt;/strong&gt;, such as custom web applications using Next.js or just plain React, this can be quite a pain.&lt;/p&gt;

&lt;p&gt;When the client wants to have the product in another language, one of the developers has to open the code and manually translate the strings, as the process is often too technical to be outsourced and fully done by the translators. &lt;/p&gt;

&lt;p&gt;Now, imagine having to translate the entire web app into more than one language - simply a buttload of manual work developers don’t have time for.&lt;/p&gt;

&lt;p&gt;We decided to see if there are any solutions to this issue on the market, did a bit of research, and decided to try out &lt;a href="https://crowdin.com/" rel="noopener noreferrer"&gt;Crowdin&lt;/a&gt; - and we think it’s awesome! It offers: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Support for almost all languages&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with 600+ apps&lt;/strong&gt; (including GitHub integration)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to use UI for translating strings&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So for our needs we developed a localization pipeline around Crowdin and wanted to share it with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localization pipeline
&lt;/h2&gt;

&lt;p&gt;In our applications, translatable strings are stored in specific &lt;strong&gt;translation files&lt;/strong&gt; (usually in &lt;strong&gt;JSON format&lt;/strong&gt;). In addition to storing translatable strings in translation files, we usually use some sort of &lt;strong&gt;internationalization framework&lt;/strong&gt; (for example &lt;a href="https://github.com/i18next/next-i18next" rel="noopener noreferrer"&gt;next-i18next&lt;/a&gt; for our Next.js applications). &lt;/p&gt;

&lt;p&gt;Example of our &lt;strong&gt;folder structure&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/project-root 
  /public 
    /locales 
      /en-INTL 
        common.json
      /sl-SI 
        common.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example &lt;strong&gt;translation file&lt;/strong&gt; &lt;code&gt;common.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"welcome_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome to our app!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"login_button"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"logout_button"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Logout"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep the translation files updated, we use &lt;a href="https://crowdin.github.io/crowdin-cli/" rel="noopener noreferrer"&gt;Crowdin CLI&lt;/a&gt; to &lt;strong&gt;upload&lt;/strong&gt; the source files and &lt;strong&gt;download&lt;/strong&gt; translations. &lt;/p&gt;

&lt;p&gt;Once the strings are available in Crowdin, our clients or dedicated translators work on translating the strings into the target languages within the Crowdin web platform.&lt;/p&gt;

&lt;p&gt;After translations are completed, Crowdin's &lt;strong&gt;integration with GitHub&lt;/strong&gt; automates the next step. Crowdin automatically generates a &lt;strong&gt;pull request&lt;/strong&gt; in the project's GitHub repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code example
&lt;/h2&gt;

&lt;p&gt;In this section, we’ll take a look at a step-by-step guide on how to implement the pipeline from the previous section.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create account and project on &lt;a href="https://crowdin.com/" rel="noopener noreferrer"&gt;Crowdin website
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Download &lt;a href="https://crowdin.github.io/crowdin-cli/" rel="noopener noreferrer"&gt;Crowdin CLI&lt;/a&gt;. If you use macOS, you can easily use &lt;strong&gt;Homebrew&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;crowdin@4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration file
&lt;/h3&gt;

&lt;p&gt;After installation, you need to &lt;strong&gt;configure the CLI&lt;/strong&gt; to work with your Crowdin project. &lt;/p&gt;

&lt;p&gt;You can do this by creating a &lt;strong&gt;configuration file&lt;/strong&gt; (&lt;code&gt;crowdin.yml&lt;/code&gt;) in your project's root directory. Configuration file can be created with following command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;After creating the configuration file, set &lt;strong&gt;Crowdin credentials&lt;/strong&gt;. You can find them in the Crowdin Web UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;project_id_env'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CROWDIN_PROJECT_ID'&lt;/span&gt;
&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;api_token_env'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CROWDIN_PERSONAL_TOKEN'&lt;/span&gt;
&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;base_path_env'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CROWDIN_BASE_PATH'&lt;/span&gt;
&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;base_url_env'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CROWDIN_BASE_URL'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;preserve_hierarchy&lt;/code&gt; property should be set to &lt;strong&gt;true&lt;/strong&gt;. This ensures that the directory structure of the source files is maintained in Crowdin. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;files&lt;/code&gt; section in the configuration file specifies which files should be synchronized with Crowdin. It includes the paths to the source files and their corresponding locations for translated files. If your source language is &lt;strong&gt;en-US&lt;/strong&gt; and translation files are located in &lt;code&gt;/public/locales/&amp;lt;locale&amp;gt;/&lt;/code&gt;, the &lt;code&gt;files&lt;/code&gt; section should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/public/locales/en-US/*.json'&lt;/span&gt;
  &lt;span class="na"&gt;translation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/public/locales/%locale%/%original_file_name%'&lt;/span&gt;
  &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/%original_file_name%'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's explain this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;source&lt;/code&gt; specifies the &lt;strong&gt;pattern&lt;/strong&gt; for files to be uploaded for translation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;translation&lt;/code&gt; specifies where the translated files should be &lt;strong&gt;placed locally&lt;/strong&gt;. &lt;code&gt;%locale%&lt;/code&gt; is a placeholder for the target language code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;dest&lt;/code&gt; indicates &lt;strong&gt;file name&lt;/strong&gt; of translations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Uploading/downloading translations from Crowdin
&lt;/h3&gt;

&lt;p&gt;To streamline the process of managing translation files with Crowdin, two &lt;strong&gt;custom commands&lt;/strong&gt; are used: &lt;code&gt;push-i18n&lt;/code&gt; and &lt;code&gt;pull-i18n&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;We’ll define these commands in the project's &lt;code&gt;package.json&lt;/code&gt; file and automate the process of uploading and downloading translation files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"push-i18n"&lt;/span&gt;: &lt;span class="s2"&gt;"crowdin upload sources &amp;amp;&amp;amp; crowdin upload translations"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;push-i18n&lt;/code&gt; command is responsible for &lt;strong&gt;pushing&lt;/strong&gt; both &lt;strong&gt;source files&lt;/strong&gt; and any &lt;strong&gt;existing translations&lt;/strong&gt; to Crowdin. This command is typically run when you want to update Crowdin with the latest version of your project’s localization files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"pull-i18n"&lt;/span&gt;: &lt;span class="s2"&gt;"crowdin download sources &amp;amp;&amp;amp; crowdin download"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pull-i18n&lt;/code&gt; command handles the process of &lt;strong&gt;downloading files&lt;/strong&gt; from Crowdin to your local project. This ensures that you have the most recent translations available in your project.&lt;/p&gt;

&lt;p&gt;To use these commands conveniently add them to the &lt;code&gt;scripts&lt;/code&gt; section of your &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;To &lt;strong&gt;upload&lt;/strong&gt; your newly updated strings just use &lt;code&gt;push-i18n&lt;/code&gt;. To &lt;strong&gt;update&lt;/strong&gt; your local strings with changes from other translators just run &lt;code&gt;pull-i18n&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Crowdin offers a great solution for a painful problem of web app localization. Instead of keeping the work on the developer side, the entire translation process can be done by non-technical team members, through an intuitive dashboard. Numerous integrations and language support are also a plus!&lt;/p&gt;

&lt;p&gt;Do you recommend any other localization tools? Let us know in the comments!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This blog and its underlying research were made by the awesome team at zerodays.dev.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>crowdin</category>
      <category>localization</category>
    </item>
    <item>
      <title>Lighthouse vs. PageSpeed Insights: The Lack of Correlation</title>
      <dc:creator>Žiga Patačko Koderman</dc:creator>
      <pubDate>Mon, 05 Aug 2024 12:23:00 +0000</pubDate>
      <link>https://dev.to/zerodays/lighthouse-vs-pagespeed-insights-the-lack-of-correlation-26d4</link>
      <guid>https://dev.to/zerodays/lighthouse-vs-pagespeed-insights-the-lack-of-correlation-26d4</guid>
      <description>&lt;h2&gt;
  
  
  What's the problem?
&lt;/h2&gt;

&lt;p&gt;Have you ever run &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed Insights (PSI)&lt;/a&gt; on your website only to receive a wildly different performance score than when running &lt;a href="https://developer.chrome.com/docs/lighthouse/overview/" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; via Chrome Developer Tools? Let me show you what I mean:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60p85qw22e5wf5kirf7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60p85qw22e5wf5kirf7k.png" alt="Comparison between Lighthouse and PageSpeed tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The overall scores are relatively similar, but there is &lt;strong&gt;almost no correlation&lt;/strong&gt; between the metrics that make them up. This is a problem because &lt;strong&gt;you cannot run PageSpeed Insights on your local build of the website&lt;/strong&gt;, forcing you to publish any changes and test them online, which is slow, inconvenient, and a bad practice. Let's take a look at where the difference between Lighthouse and PageSpeed comes from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reason for the discrepancy
&lt;/h2&gt;

&lt;p&gt;PageSpeed uses Lighthouse under the hood, so what is the deal here? Googling this tells you that PageSpeed also includes some real-world data and so on, but this does not address the problem at stake. After some lengthy deep dives into the issue, we found the reason for it &lt;a href="https://github.com/GoogleChrome/lighthouse/blob/main/docs/throttling.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PageSpeed Insights run Lighthouse using a relative 4x CPU slowdown.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fact that it is &lt;strong&gt;relative&lt;/strong&gt; results in a different score based on the device running the benchmark.&lt;/p&gt;

&lt;p&gt;You might be tempted to say "just multiply the times here by X to compensate for having a faster computer." However, this won't work. We won't go into the depths of why this is the case beyond saying that those metrics are complex and don't scale in that way. You can find out more &lt;a href="https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/" rel="noopener noreferrer"&gt;here&lt;/a&gt; (TBT is perhaps the simplest example of that).&lt;/p&gt;

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

&lt;p&gt;Let us start by installing the tools we'll need further down the line.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install the Lighthouse CLI
&lt;/h3&gt;

&lt;p&gt;You cannot tweak the CPU slowdown of a Lighthouse simulation through the Chrome Dev Tools. Hence the need for &lt;a href="https://github.com/GoogleChrome/lighthouse" rel="noopener noreferrer"&gt;Lighthouse CLI&lt;/a&gt;. You can install it using&lt;/p&gt;

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

npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; lighthouse


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  2. Estimate the slowdown factor (optional)
&lt;/h3&gt;

&lt;p&gt;Here we will show how to extract the &lt;code&gt;benchmarkIndex&lt;/code&gt; for your device and then calculate an approximate slowdown factor that you need to use. This is the procedure found in the Chrome documentation. However, you will probably need to readjust the slowdown even after that. Therefore, &lt;strong&gt;we prefer to skip this step and find a suitable constant using trial and error&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  2.1 Extract &lt;code&gt;benchmarkIndex&lt;/code&gt; from the report
&lt;/h4&gt;

&lt;p&gt;Here is a command that extracts the &lt;code&gt;benchmarkIndex&lt;/code&gt; from the report. It presumes that you have &lt;code&gt;jq&lt;/code&gt; installed on your system.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

lighthouse YOUR_WEBSITE_URL &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="nt"&gt;--chrome-flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--headless"&lt;/span&gt; &lt;span class="nt"&gt;--form-factor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mobile | jq &lt;span class="s1"&gt;'.environment.benchmarkIndex'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;On a baseline 2021 M1 MacBook Pro, this number is around 2900.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.2 Convert &lt;code&gt;benchmarkIndex&lt;/code&gt; into a slowdown estimate
&lt;/h4&gt;

&lt;p&gt;You can use the &lt;a href="https://lighthouse-cpu-throttling-calculator.vercel.app/" rel="noopener noreferrer"&gt;CPU Throttling Calculator&lt;/a&gt; to estimate the slowdown factor you need. For &lt;code&gt;benchmarkIndex&lt;/code&gt; 2900, this yields 9.9. However, it later turns out that this is not enough by almost a factor of two. &lt;/p&gt;

&lt;p&gt;Let's take a look at why this might be the case. By reverse engineering the website code, we found that this calculator uses a linear estimation based on a few calibrated points. Let's take a look at a chart of their estimation function:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fql6rhyjgnpt9kqr6wvic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fql6rhyjgnpt9kqr6wvic.png" alt="CPU Throttling Calculator Estimation Function"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last calibrated point is around benchmark index 1300, more than two times lower than what a modern laptop is capable of. This explains why the estimated CPU slowdown of around 10 does not produce satisfactory results. In other words, it is not (yet) calibrated in the range that matters to us.&lt;/p&gt;

&lt;p&gt;For a baseline 2021 M1 MacBook Pro, the slowdown factor that produces reasonable results sits around 19.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Run Lighthouse
&lt;/h3&gt;

&lt;p&gt;You can run Lighthouse with a desired CPU slowdown like this:&lt;/p&gt;

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

lighthouse YOUR_WEBSITE_URL &lt;span class="nt"&gt;--throttling&lt;/span&gt;.cpuSlowdownMultiplier YOUT_CPU_SLOWDOWN &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;html &lt;span class="nt"&gt;--chrome-flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--headless"&lt;/span&gt; &lt;span class="nt"&gt;--form-factor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mobile &lt;span class="nt"&gt;--output-path&lt;/span&gt; report.html


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

&lt;/div&gt;

&lt;p&gt;and then open up the generated HTML report.&lt;/p&gt;

&lt;p&gt;This produces somewhat more relevant results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9plweix99qqurcmcpzb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9plweix99qqurcmcpzb9.png" alt="Comparison between PageSpeed Insight and local Lighthouse run using CPU slowdown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The values are not the same, but we should not expect them to be — this is a complex benchmark running on a simulated device, after all. Go ahead and &lt;strong&gt;tweak your slowdown factor&lt;/strong&gt; until it resembles what online PageSpeed Index test is telling you.&lt;/p&gt;

&lt;p&gt;Here is a one-liner using a JSON report that also extracts the metrics in question:&lt;/p&gt;

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

lighthouse YOUR_WEBSITE_URL &lt;span class="nt"&gt;--throttling&lt;/span&gt;.cpuSlowdownMultiplier YOUT_CPU_SLOWDOWN &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="nt"&gt;--chrome-flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--headless"&lt;/span&gt; &lt;span class="nt"&gt;--form-factor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mobile | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'{LCP: .audits["largest-contentful-paint"].numericValue, "LCP Score": .audits["largest-contentful-paint"].score, FCP: .audits["first-contentful-paint"].numericValue, "FCP Score": .audits["first-contentful-paint"].score, SI: .audits["speed-index"].numericValue, "SI Score": .audits["speed-index"].score, TBT: .audits["total-blocking-time"].numericValue, "TBT Score": .audits["total-blocking-time"].score, CLS: .audits["cumulative-layout-shift"].numericValue, "CLS Score": .audits["cumulative-layout-shift"].score, "TOTAL": .categories.performance.score} | to_entries[] | "\(.key): \(.value)"'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;It is not the prettiest, but it can be useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Repeat multiple times
&lt;/h3&gt;

&lt;p&gt;The last thing we wanted to touch on is repeatability. Lighthouse will produce vastly different performance results each run. Therefore, we usually like to &lt;strong&gt;run it at least 5 times&lt;/strong&gt; and compute the averages. This allows us to have a better sense of whether we are moving in the right direction.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://gist.github.com/zigapk/4c627c25d9de61d701e674c7bc89ad57" rel="noopener noreferrer"&gt;gist here&lt;/a&gt; contains a script that does just that for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Dive deeper
&lt;/h3&gt;

&lt;p&gt;The Lighthouse JSON report contains much more information that the HTML one, so it is worth examining a bit. It contains all the reasons why the webpage might not be performing well. One of the more useful things we found is the &lt;code&gt;largest-contentful-paint-element&lt;/code&gt;, which tells you which element you should optimize in order to achieve a better LCP score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get back to work
&lt;/h2&gt;

&lt;p&gt;Well, now the hard part begins - improving the webpage performance. However, do not forget that Lighthouse is a (very relevant) made-up metric - in reality, you need to optimize your webpage for real-world users. And web crawlers, but we will get into the SEO debate some other time.&lt;/p&gt;

&lt;p&gt;This blog post was written by the awesome team at &lt;a href="https://zerodays.dev/" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>tutorial</category>
      <category>web</category>
    </item>
    <item>
      <title>Goodbye Webflow, Hello Our Shiny New Website</title>
      <dc:creator>Špela Buh</dc:creator>
      <pubDate>Tue, 23 Jul 2024 06:38:25 +0000</pubDate>
      <link>https://dev.to/zerodays/goodbye-webflow-hello-our-shiny-new-website-1m31</link>
      <guid>https://dev.to/zerodays/goodbye-webflow-hello-our-shiny-new-website-1m31</guid>
      <description>&lt;p&gt;&lt;em&gt;“That's it! I'm done with Webflow! It's been nothing but a nightmare. We're moving to a headless CMS, and we're doing it now. No more wasting time!”...&lt;/em&gt; is what could have easily been heard about two months ago in our office, after (yet again) discussing whether to make a move to a headless CMS for our company website or not. &lt;/p&gt;

&lt;p&gt;In reality, the discussion was much longer and much more boring to sum up here. But what is true, is that for the past few months we have been working hard to revamp our website after growing frustrated with Webflow, and we finally launched it! So, after completing this strenuous path, we decided to share the journey insights with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short history of what we tried and why it was time to move on.
&lt;/h2&gt;

&lt;p&gt;So, as evident from the title, we of course used Webflow, but we also tried out WordPress, as well as hard coding our website using React and considered a few other alternatives. However, each of these options was in some way too limiting for our needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hard coded website&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Took too long to set up.&lt;/li&gt;
&lt;li&gt;Difficult to edit content.&lt;/li&gt;
&lt;li&gt;Can’t outsource the editing work to a non-technical team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Webflow&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited by the selected theme.&lt;/li&gt;
&lt;li&gt;It requires more technical understanding than expected - when editing the content, you can easily change something on one page that changes the layout, or some other design aspects on other pages as well. This was at least in part a problem with the theme we were using, but Webflow did not help either.&lt;/li&gt;
&lt;li&gt;There are some live preview issues.&lt;/li&gt;
&lt;li&gt;Localization was not built-in until very recently.&lt;/li&gt;
&lt;li&gt;The customization is very hard and a lot of things were taken out of our hands (performance, fine-grained SEO, lazy loading, etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;WordPress&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance is subpar.&lt;/li&gt;
&lt;li&gt;Localization issues in the headless version.&lt;/li&gt;
&lt;li&gt;Bottom line: If you are using WordPress as a template it works fine but as a headless CMS it mostly sucks because of subpar API support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also considered Squarespace, Wix and Framer, and although it is possible to use them as headless CMS, they are also quite limiting. So we decided to find a suitable headless CMS, meaning we could use Next.js (our beloved framework) for frontend development and develop a completely custom website, but still have a content management system, which allows a relatively simple input and editing of content.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we were looking for:
&lt;/h2&gt;

&lt;p&gt;Based on our experience we decided to go for a suitable headless CMS for managing the content of a Next.js-based landing page, allowing full customization and seamless integration. Other requirements we had were also support of custom sections or components that emulate a purposely primitive website builder, rich text editor support, user-friendly admin interface, SEO support, multi-user support, media hosting and easy backups and restoring.&lt;/p&gt;

&lt;p&gt;We conducted quite an extensive research when it came to choosing the right solution for us. As this was long enough for a whole separate blog post, we decided to post it here: &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/zerodays" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F8603%2F70b6113f-d2d4-4468-993c-ca9a42e965b0.png" alt="zerodays" width="512" height="512"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1416019%2Fe6b057e6-a1c4-43a8-b6c4-5dec4d978016.jpeg" alt="" width="460" height="460"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/zerodays/the-hunt-for-a-perfect-headless-cms-123h" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;The Hunt for a Perfect Headless CMS&lt;/h2&gt;
      &lt;h3&gt;Žiga Patačko Koderman for zerodays ・ Jul 3&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#nextjs&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#cms&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#payload&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;In the end, we (web)landed on Payload because of its superb integration with Next.js - our technology of choice for web development. &lt;/p&gt;

&lt;p&gt;This approach allows us to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have absolute control over performance.&lt;/li&gt;
&lt;li&gt;Play around with SEO and analytics on a lower level than with other solutions.&lt;/li&gt;
&lt;li&gt;Have a great experience for the content editors.&lt;/li&gt;
&lt;li&gt;Build a tailored website including great animations and easter eggs.&lt;/li&gt;
&lt;li&gt;Use our favorite libraries and tools along the way (Framer Motion, Tailwind CSS, shadcn/ui among others - take a look at our Next.js template on GitHub).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The results speak for themselves.&lt;/p&gt;

&lt;p&gt;Desktop performance&lt;br&gt;
&lt;a href="https://pagespeed.web.dev/analysis/https-www-zerodays-dev-en/0f7epfa9hs?form_factor=desktop" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht190wi1305ctmmqrkp5.png" alt="Lighthouse performance report for desktop" width="724" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mobile performance&lt;br&gt;
&lt;a href="https://pagespeed.web.dev/analysis/https-www-zerodays-dev-en/0f7epfa9hs?form_factor=mobile" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvye760gwzjqmllmevpfi.png" alt="Lighthouse performance report for mobile" width="700" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So far, we are loving it, but what we love even more is the new website, built by our team of talented developers. &lt;a href="https://www.zerodays.dev/en" rel="noopener noreferrer"&gt;Check it out&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Here are also some before and after shots, so you can compare the improvement for yourself: &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Landing page before:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlhknhazzl6nt1hkvimw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlhknhazzl6nt1hkvimw.png" alt="Landing page on the old website - screenshot" width="622" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Landing page after:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2atwyhtjr0kbbqfuhgqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2atwyhtjr0kbbqfuhgqx.png" alt="Landing page on the new website - screenshot" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;About Us page before:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qr59rb5p592y3yr3ukr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qr59rb5p592y3yr3ukr.png" alt="About us page on the old website - screenshot" width="642" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;About Us page after:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04mp0inhi54dexk24ygj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04mp0inhi54dexk24ygj.png" alt="About us page on the new website - screenshot" width="800" height="939"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Visualization of our development process before:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45fmgsagxa93z38z3c9e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45fmgsagxa93z38z3c9e.png" alt="Visualization of our development process on the old website - screenshot" width="690" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visualization of our development process after:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltt0toro61bmlpv6dh88.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltt0toro61bmlpv6dh88.png" alt="Visualization of our development process on the new website - screenshot" width="800" height="864"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Services page before:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flng10orfmdly7xtj1k6z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flng10orfmdly7xtj1k6z.png" alt="Services page on the old website - screenshot" width="640" height="864"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Services page after:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bumdj2kd2vayhb4i5w3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bumdj2kd2vayhb4i5w3.png" alt="Services page on the new website - screenshot" width="800" height="867"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Contact form before:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftywqd7j8fqoasgp33hnf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftywqd7j8fqoasgp33hnf.png" alt="Contact form page on the old website - screenshot" width="478" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Contact form after:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbkaivxy9mn9tpplg7r7j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbkaivxy9mn9tpplg7r7j.png" alt="Contact form page on the new website - screenshot" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>website</category>
      <category>payload</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>How Our Infrastructure Supports Last-Minute Studying</title>
      <dc:creator>Žiga Patačko Koderman</dc:creator>
      <pubDate>Thu, 04 Jul 2024 08:15:21 +0000</pubDate>
      <link>https://dev.to/zerodays/how-our-infrastructure-supports-last-minute-studying-2c5d</link>
      <guid>https://dev.to/zerodays/how-our-infrastructure-supports-last-minute-studying-2c5d</guid>
      <description>&lt;p&gt;The past few weeks, one of our clients, &lt;a href="https://astra.si/en/astra-ai/" rel="noopener noreferrer"&gt;Astra AI&lt;/a&gt; - an AI powered math tutor, has been recording a steep increase in traffic. This makes sense, as in June, students were frantically studying to improve their final grades right before the school year ended and preparing for the Slovenian national high school final exam - Matura.&lt;/p&gt;

&lt;p&gt;Although it’s been years since our teachers warned us not to study in the last days and avoid cramming, the data shows that this is still the case. Well, little has changed since our study days (not that we listened to teachers back then either, of course).&lt;/p&gt;

&lt;p&gt;But this time we are experiencing this phenomenon from a completely new perspective - intensely studying in the last few days before the exam means a sudden increase in traffic. The chart below represents the number of &lt;a href="https://platform.openai.com/tokenizer" rel="noopener noreferrer"&gt;OpenAI tokens&lt;/a&gt; used by Astra per day:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fap66flywkqll97g9xr19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fap66flywkqll97g9xr19.png" alt="Chart showing a big spike in token usage." width="526" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We won’t be sharing the absolute numbers of course, but suffice to say that we believe this to be one of the biggest usages in our region.&lt;/p&gt;

&lt;p&gt;This was Astra’s first time through such (admittedly expected) load increase. However, we were fairly confident in our infrastructure and did nothing special in advance to handle this. And in fact no technical issues arose.&lt;/p&gt;

&lt;p&gt;Since the peak, we’ve analyzed the logs, reviewed the data, and attributed this to a few key factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some clever load balancing between multiple API keys in order to climb &lt;a href="https://platform.openai.com/docs/guides/rate-limits/usage-tiers" rel="noopener noreferrer"&gt;OpenAI's tier ladder&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Great choice of hosting providers allowing for easy elastic scaling, namely:

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; for the &lt;a href="http://Next.js" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; frontend and part of the backend. The killer features for us include autoscaling, CDN and instant rollbacks out of the box 📦.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://railway.app/" rel="noopener noreferrer"&gt;Railway.app&lt;/a&gt; for the background machinery handling the more complicated requests.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Great choice of monitoring and analytics tools (&lt;a href="https://sentry.io/welcome/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;, &lt;a href="https://axiom.co/" rel="noopener noreferrer"&gt;Axiom&lt;/a&gt;, &lt;a href="https://posthog.com/" rel="noopener noreferrer"&gt;Posthog&lt;/a&gt; and &lt;a href="https://github.com/louislam/uptime-kuma" rel="noopener noreferrer"&gt;Uptime Kuma&lt;/a&gt;) coupled with amazing &lt;a href="https://slack.com/" rel="noopener noreferrer"&gt;Slack&lt;/a&gt; integrations that allowed us to iron out any issues way before the traffic spike while the troubling features were still fresh from the oven.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;As we could talk about our infrastructure choices for days, we decided to keep this post short and simple but are planning on doing more of a deep dive in one of the future posts, so keep an eye out for that.&lt;/p&gt;

&lt;p&gt;This blog post was written by the &lt;a href="https://zerodays.dev/" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt; team.&lt;/p&gt;

</description>
      <category>openai</category>
      <category>monitoring</category>
      <category>startup</category>
      <category>vercel</category>
    </item>
    <item>
      <title>The Hunt for a Perfect Headless CMS</title>
      <dc:creator>Žiga Patačko Koderman</dc:creator>
      <pubDate>Wed, 03 Jul 2024 06:57:50 +0000</pubDate>
      <link>https://dev.to/zerodays/the-hunt-for-a-perfect-headless-cms-123h</link>
      <guid>https://dev.to/zerodays/the-hunt-for-a-perfect-headless-cms-123h</guid>
      <description>&lt;p&gt;We recently decided to leave Webflow (the reasons why will be further explained in our next blog) and migrate our company website to a headless content management system. For that reason we decided to thoroughly research the options available and select the best solution for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  🗒️ Requirements (and wishes ✨)
&lt;/h2&gt;

&lt;p&gt;Our main goal was to select a suitable headless CMS for managing the content of a NextJS-based landing page, allowing for full customization and seamless integration. Based on that we developed a list of requirements for the system we wanted, not just for our website, but also for the majority of other landing pages we develop for our clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Must have&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Localization support (&lt;a href="https://en.wikipedia.org/wiki/Right-to-left_script" rel="noopener noreferrer"&gt;RTL&lt;/a&gt; support in editor is a bonus)&lt;/li&gt;
&lt;li&gt;Good integration with &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;NextJS&lt;/a&gt;:&lt;/li&gt;
&lt;li&gt;Good &lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt; integration.&lt;/li&gt;
&lt;li&gt;Possible server-side (pre)rendering.&lt;/li&gt;
&lt;li&gt;Supports custom sections or components to emulate a purposely primitive website builder.&lt;/li&gt;
&lt;li&gt;Rich text editor support.&lt;/li&gt;
&lt;li&gt;User-friendly admin interface.&lt;/li&gt;
&lt;li&gt;SEO support.&lt;/li&gt;
&lt;li&gt;Multi-user support.&lt;/li&gt;
&lt;li&gt;Media hosting.&lt;/li&gt;
&lt;li&gt;Easy backup and restore.&lt;/li&gt;
&lt;li&gt;Good reputation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nice to have&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open source.&lt;/li&gt;
&lt;li&gt;Ability to self-host.&lt;/li&gt;
&lt;li&gt;Support for drafts and versioning&lt;/li&gt;
&lt;li&gt;Live preview inside the content editor.&lt;/li&gt;
&lt;li&gt;Admin panel customization options.&lt;/li&gt;
&lt;li&gt;A/B testing support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Other considerations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migration challenges from the existing website.&lt;/li&gt;
&lt;li&gt;Multi-region hosting and caching (can be done on NextJS side).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on those requirements we tested and considered lots of CMS options. Let’s jump straight into why the Payload ended up our first choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  🥇 The winner - &lt;a href="https://payloadcms.com/" rel="noopener noreferrer"&gt;Payload CMS&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reasons for Selection&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excellent integration with NextJS. Latest version (3.0) is now a part of a NextJS application.&lt;/li&gt;
&lt;li&gt;Native localization support where localization can be done per field, instead of only per-block.&lt;/li&gt;
&lt;li&gt;Open source and widely used.&lt;/li&gt;
&lt;li&gt;Ability to be self-hosted.&lt;/li&gt;
&lt;li&gt;Admin dashboard is easy to extend with custom React components.&lt;/li&gt;
&lt;li&gt;Easy to extend with custom functionalities since CMS code is co-located with the frontend.&lt;/li&gt;
&lt;li&gt;Native TypeScript support.&lt;/li&gt;
&lt;li&gt;Live editing preview option.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt; The version 3 that includes all the sweet features we desire is still in beta - this is why we are using it only for our website at the moment and not experimenting on our clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🥈 Other CMS systems considered
&lt;/h2&gt;

&lt;p&gt;Below is the list of all the other options we’ve tested. This is not to say that none of those could work for us - Strapi was extremely close too, and others might be best suited for your specific use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://strapi.io/" rel="noopener noreferrer"&gt;Strapi&lt;/a&gt;
&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;Open source.&lt;/li&gt;
&lt;li&gt;(Probably) the most popular headless CMS option.&lt;/li&gt;
&lt;li&gt;Self-hosted easily.&lt;/li&gt;
&lt;li&gt;Official plugin to generate Swagger documentation so NextJS integration is trivial (although generated schema doesn’t include localization parameters).&lt;/li&gt;
&lt;li&gt;Native localization support&lt;/li&gt;
&lt;li&gt;Customizable admin dashboard.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Locales are still separate, when translating content all fields need to be set in all locales instead of preserving some fields (images, slugs, etc.). This might cause some friction for editors.&lt;/li&gt;
&lt;li&gt;Poor TypeScript support.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://wordpress.org/" rel="noopener noreferrer"&gt;WordPress&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Very popular option, familiarity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Requires additional plugins and configuration to achieve full headless functionality. Worse NextJS integration and development experience. Forces you to use different technologies on frontend and backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.sanity.io/" rel="noopener noreferrer"&gt;Sanity&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: NextJS support, UI editor integration, flexible localization methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: No self hosting, high price (per user, per request and bandwidth).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://prismic.io/" rel="noopener noreferrer"&gt;Prismic&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: NextJS support, live preview editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: No self-hosting, clunky localization (all locales are totally separate), restrictive pricing plans (max 8 locales in platinum plan).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://tina.io/" rel="noopener noreferrer"&gt;Tina.io&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Simple, developer friendly, has live editing capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Poor i18n support, needs to be handled by developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://directus.io/" rel="noopener noreferrer"&gt;Directus&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: More a dataset editor than a CMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.contentful.com/" rel="noopener noreferrer"&gt;Contentful&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Limited language support without enterprise tier.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://keystonejs.com/" rel="noopener noreferrer"&gt;KeystoneJS&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Seems inactive, development progress on the official website was not updated since 2022.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.contentstack.com/" rel="noopener noreferrer"&gt;Contentstack&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: CMS is a minor part of the product. Company’s focus is elsewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.storyblok.com/" rel="noopener noreferrer"&gt;Storyblok&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Expensive. Subpar developer experience.&lt;/p&gt;

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

&lt;p&gt;In the end, Payload took the crown for us. What ultimately sold us on it, was its superb integration with Next.js - our technology of choice for web development.&lt;/p&gt;

&lt;p&gt;This blog and its underlying research was made by the awesome team at &lt;a href="https://zerodays.dev/" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>cms</category>
      <category>payload</category>
      <category>webdev</category>
    </item>
    <item>
      <title>All Paths Lead To Jira</title>
      <dc:creator>Žiga Patačko Koderman</dc:creator>
      <pubDate>Wed, 22 May 2024 07:03:24 +0000</pubDate>
      <link>https://dev.to/zerodays/all-paths-lead-to-jira-2b43</link>
      <guid>https://dev.to/zerodays/all-paths-lead-to-jira-2b43</guid>
      <description>&lt;h2&gt;
  
  
  zerodays’ journey through the world of project management tools
&lt;/h2&gt;

&lt;p&gt;Right after we started our company in late 2021, we knew a good project management tool will be absolutely essential for our work, but we had some specific requirements. We do a lot of agency work, developing for multiple different clients at the same time so the tool had to be amazing for cross project planning, as well as enable some type of transparent sharing of the project progress with the client. &lt;/p&gt;

&lt;p&gt;Now, almost two and a half years later and with four tools tested in detail, few others considered and most of them tossed, we feel like we finally landed on a good option for us - Jira. But, as this was definitely not a smooth path, we wanted to do an overlook of everything we have tried, why it worked and why not, in hopes it eases the process for anyone else trying to navigate the world of project management tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What methodology do we follow?
&lt;/h2&gt;

&lt;p&gt;The methodology we follow of course defines the tools needed for our project management, so let’s start with that. We follow agile principles, but no specific methodology strictly, however, we use Kanban boards. So, we were looking for a tool that would offer us flexible and user-friendly Kanban boards and would allow us to plan as well as track the time spent on various tasks/tickets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying out the Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Issues and Projects
&lt;/h3&gt;

&lt;p&gt;Our first project management tool we took on was GitHub Projects. We knew and loved GitHub Issues from the open source projects and believed that Projects would be the tool that would fit us. However, unfortunately, it left quite a lot to be desired.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we disliked&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All the project management is connected only to the repository. So if your project exists in two separate repositories (frontend and backend for example), you have to make two separate tickets instead of one and cross linking through the repositories is not a practical solution.&lt;/li&gt;
&lt;li&gt;It isn’t really a fit for our office team, as the non-development tasks don’t fit in, and it’s expensive as a project management tool.&lt;/li&gt;
&lt;li&gt;Every ticket you create is a draft by default (this isn’t really a deal-breaker on its own, but it definitely makes the user experience a lot less enjoyable).&lt;/li&gt;
&lt;li&gt;There is no hierarchy when it comes to tickets, which makes having an overview of the issue timeline, the main issue, the sub issues etc. difficult.&lt;/li&gt;
&lt;li&gt;Poor time tracking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So after a while, we made the decision to move on and try out Clickup 2.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  ClickUp
&lt;/h3&gt;

&lt;p&gt;ClickUp is advertised as an “all in one productivity platform” which drew us to it. It definitely solved most of the issues we mentioned with GitHub, such as cross project tracking and ticket hierarchy, but unfortunately we still weren’t completely satisfied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we disliked&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firstly, the app had a lot of bugs and issues, which made it difficult to use. For example, often when creating a ticket, it didn’t show up until a couple of minutes later, when two same tickets popped up. Slow loading times were also a common issue.&lt;/li&gt;
&lt;li&gt;The layout and organization of the platform was in our opinion a bit cluttered, so the project tracking and overlook of the projects is messy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have to mention that the bugs mentioned above are supposedly resolved in the 3.0 version, however as the app didn’t convince us, we decided not to stay aboard, and jumped ship to Notion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notion
&lt;/h3&gt;

&lt;p&gt;Notion is an everyday organization tool, combining project management tools with wikis, docs, planning tools, etc. Its customizability and the lure of having one place for the documentation and project management are what convinced us to try it. We used it for most of our development and non-development documentation and tried out two Kanban boards - a free Kanban template offered by the platform, and our own customizable Kanban board. What we liked about the platform was the easy addition of external guests and sharing the projects with people outside of the company, and good tools for non-development related team collaboration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we disliked&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Although Notion is quite known to be very customizable for everyday use, it was too limited for our development team. We specifically had issues with the Kanban boards - they were too rigid. Both the template and our own version lacked the ability to move, rename columns, add filters, automatically hide done tickets, etc. &lt;/li&gt;
&lt;li&gt;Poor GitHub integration.&lt;/li&gt;
&lt;li&gt;Sometimes it doesn't work - some tickets are not saved on the first try and other bugs.&lt;/li&gt;
&lt;li&gt;Similar to GitHub - there was no ticket hierarchy, which made the project overlook difficult.&lt;/li&gt;
&lt;li&gt;Columns don’t have an option to hide tickets, so it gets cluttered.&lt;/li&gt;
&lt;li&gt;Project planning and filter options were quite limited. There was also no option to integrate it with our time-tracking tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With enough experience to finally know what we like and what not, we started considering our final move to either Linear or Jira.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jira
&lt;/h3&gt;

&lt;p&gt;In the end we settled on Jira, because of the configurability of the tool and integrations it offers, specifically with Slack, Figma and GitHub, the last one being absolutely amazing. So far, it solves the majority of the issues we had with other tools, most notably, it is automated where it can be, which makes a more hassle-free user experience for the development team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do we still dislike&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Although we love the configurability it offers, it is quite difficult to set up everything - it is confusing, cluttered in different places, and the learning curve is very steep. On the good side, it is only a pain for the admins, not the developers.&lt;/li&gt;
&lt;li&gt;It doesn’t really offer good template options. The way around we found is that we have a project that we named template and we then initiate new projects based on it.. The downside is we still have to manually set up filters and settings, so it takes a while. Again, thankfully it is the issue only for the admins.&lt;/li&gt;
&lt;li&gt;We had to make our own integration for the time tracking software we use - Clockify. There are some apps that offer some integration, but we wanted time tracking to be synced with Jira’s own time tracking. However, with Jira, we at least had an option to do this ourselves.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping it up
&lt;/h2&gt;

&lt;p&gt;We also considered Linear and although it seemed to cover most of our requirements, we decided to go for Jira because of the integrations it offers. In all honesty, we have to admit that we might have not considered it enough and will maybe return back to it at some point, but a decision had to be made.&lt;/p&gt;

&lt;p&gt;So, here we are at last - Jira. Is this the best solution for eternity? Probably not. But, we are happy at the moment, as we configured it to our specific needs and it doesn’t restrict our developers, while being fairly automated, so the hassle of moving tickets etc. is minimized. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;zerodays is a custom software development team based in Ljubljana, Slovenia. We focus on AI development and consulting, web and mobile app development, business digitalization, and process automation solutions. If you’re interested in what we do and want to keep up with us, visit our &lt;a href="https://zerodays.dev/" rel="noopener noreferrer"&gt;website&lt;/a&gt; or follow us on &lt;a href="https://si.linkedin.com/company/zerodays" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>projectmanagement</category>
      <category>jira</category>
      <category>projectmanagementtools</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Generative UI in React Native</title>
      <dc:creator>Žiga Patačko Koderman</dc:creator>
      <pubDate>Thu, 11 Apr 2024 11:09:47 +0000</pubDate>
      <link>https://dev.to/zerodays/generative-ui-in-react-native-180f</link>
      <guid>https://dev.to/zerodays/generative-ui-in-react-native-180f</guid>
      <description>&lt;p&gt;Let's build a generative UI Weather Chatbot!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZHJ4NDJndHk5NnRwNzI5dmZ0ODdoMms1bGMwZzUyc2Q4M2t3NzBsbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ENgvOtmRyu7VSuOY7r/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZHJ4NDJndHk5NnRwNzI5dmZ0ODdoMms1bGMwZzUyc2Q4M2t3NzBsbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ENgvOtmRyu7VSuOY7r/giphy.gif" width="222" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article is inspired by &lt;a href="https://sdk.vercel.ai/docs/concepts/ai-rsc"&gt;Vercel's Generative UI&lt;/a&gt;. It expands on &lt;a href="https://platform.openai.com/docs/guides/function-calling"&gt;OpenAI's functions/tools&lt;/a&gt; by allowing the model to visualize data to the user.&lt;/p&gt;

&lt;p&gt;Let's get straight into it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create an Expo Typescript app 📱
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-expo-app &lt;span class="nt"&gt;-t&lt;/span&gt; expo-template-blank-typescript demo
&lt;span class="nb"&gt;cd &lt;/span&gt;demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install 📦
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add react-native-gen-ui zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds the &lt;a href="https://www.npmjs.com/package/react-native-gen-ui"&gt;&lt;code&gt;react-native-gen-ui&lt;/code&gt;&lt;/a&gt; package that offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exposes &lt;code&gt;useChat&lt;/code&gt; hook for easy access to &lt;strong&gt;OpenAI's completions&lt;/strong&gt; api.&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;streaming&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enables &lt;strong&gt;Generative UI&lt;/strong&gt; via &lt;code&gt;tools&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Is fully &lt;strong&gt;type-safe&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The package was initially developed by us at &lt;a href="https://zerodays.dev/"&gt;zerodays.dev&lt;/a&gt; and was open-sourced for everyone to use.&lt;/p&gt;

&lt;p&gt;We also install &lt;code&gt;zod&lt;/code&gt; here for validation of tool parameters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Import dependencies
&lt;/h3&gt;

&lt;p&gt;At the top of the &lt;code&gt;App.tsx&lt;/code&gt; add&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;z&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;zod&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;OpenAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isReactElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useChat&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-gen-ui&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;h3&gt;
  
  
  Initialize OpenAI
&lt;/h3&gt;

&lt;p&gt;Below imports, write:&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="nx"&gt;openAi&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;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPO_PUBLIC_OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4&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;Then make sure to add &lt;code&gt;EXPO_PUBLIC_OPENAI_API_KEY&lt;/code&gt; to your environment or in &lt;code&gt;.env&lt;/code&gt; file. You can obtain an API key in the &lt;a href="https://platform.openai.com/account/api-keys"&gt;OpenAI Platform&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Utilize &lt;code&gt;useChat&lt;/code&gt; hook.
&lt;/h3&gt;

&lt;p&gt;At the top of the App function, let's use the &lt;code&gt;useChat&lt;/code&gt; hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onInputChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;openAi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;initialMessages&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;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a nice little weather chatbot.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&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;Here we are passing initial messages into the &lt;code&gt;useChat&lt;/code&gt; hook, then allow it to manage the state of messages, stream the content from OpenAI and update the UI.&lt;/p&gt;

&lt;p&gt;But we are still missing a way to render this - make the function return the snippet below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;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="nc"&gt;View&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;flex&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;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#fff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&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="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="cm"&gt;/* Iterate over all messages and render them */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Message can be either a React component or a string&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isReactElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Render user messages in blue&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&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;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&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="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;// Render assistant messages&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;Text&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&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;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&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="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;// This includes tool calls, tool results and system messages&lt;/span&gt;
          &lt;span class="c1"&gt;// Those are visible to the model, but here we hide them to the user&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Text input for chatting with the model */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;borderColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gray&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;borderWidth&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;textAlign&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100%&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="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;autoFocus&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onChangeText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onInputChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;
      &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Send"&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;isLoading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&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;Then run the app using &lt;code&gt;npx expo start&lt;/code&gt;. More details about running an Expo app can be found &lt;a href="https://docs.expo.dev/get-started/expo-go/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Voilà - we can have a basic chat with 🤖 now!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnEyOGlqN25heTA3dTIydnNmaWFwcGtuMnFjZjByOGlwNjUxMDZsZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/031z4f7fdA9RGk2EZE/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnEyOGlqN25heTA3dTIydnNmaWFwcGtuMnFjZjByOGlwNjUxMDZsZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/031z4f7fdA9RGk2EZE/giphy.gif" width="221" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a tool
&lt;/h3&gt;

&lt;p&gt;Now the real magic begins 🪄. A tool is defined by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;its name,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; (a simple string for model to understand what this tool does),&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parameters&lt;/code&gt; (a type-safe zod schema of what this tool accepts as arguments) and&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;render&lt;/code&gt; function for handling what both the user and model see when this tool is called.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's add a “get weather tool“ to tell us what the weather is like at a certain location:&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="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;getWeather&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Get weather for a location&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Parameters for the tool&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="c1"&gt;// Render component for weather - can yield loading state&lt;/span&gt;
      &lt;span class="na"&gt;render&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="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fetch the weather data&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getWeatherData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Return the final result&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// The data will be seen by the model&lt;/span&gt;
          &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="c1"&gt;// The component will be rendered to the user&lt;/span&gt;
          &lt;span class="na"&gt;component&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="nc"&gt;View&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rgba(20, 20, 20, 0.05)&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="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sunny&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;☀️&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;🌧️&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="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&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;},&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 allows the model to call the &lt;code&gt;getWeather&lt;/code&gt; function. The model receives the &lt;code&gt;data&lt;/code&gt; and can comment upon it, while the user is presented with an emoji representation of the weather (either ☀️ or 🌧️).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;getWeatherData&lt;/code&gt; function is still missing, let's add it:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getWeatherData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&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="c1"&gt;// Wait 3 seconds to simulate fetching the data from an API&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Randomly return either `sunny` or `rainy`&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sunny&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;rainy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a chat interface that goes beyond just words and can display custom components (even though, this is just emojis in our example).&lt;br&gt;
&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZXJzYW5lZzRpeGhzNDRoMXVpcWw1dno5MXd4b2hvNXY0ODU5MG5jdyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EhoBpWpuozdWd99wyO/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZXJzYW5lZzRpeGhzNDRoMXVpcWw1dno5MXd4b2hvNXY0ODU5MG5jdyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EhoBpWpuozdWd99wyO/giphy.gif" width="221" height="480"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Show loading states
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;react-native-gen-ui&lt;/code&gt; allows us to yield zero or more components before returning the actual data and the final component. This can be used to display progress to the user until the data is fetched.&lt;/p&gt;

&lt;p&gt;Add the following code at the beginning of &lt;code&gt;render&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;render&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="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Yield a loading indicator&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ActivityIndicator&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"activity-indicator"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Fetch the weather data&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;render&lt;/code&gt; function is in fact a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator"&gt;generator&lt;/a&gt; - this is denoted by the &lt;code&gt;*&lt;/code&gt; after the &lt;code&gt;function&lt;/code&gt; keyword. The interface will always display the last component that was either yielded or returned, allowing us to swap what the user sees over time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExeXplcnd5cXJoNmR6azhtdXY5cDdzM3NxZGJ0cXo2MmFqOGpmdjRzbSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l66xLVIZbm3Y6PGrxj/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExeXplcnd5cXJoNmR6azhtdXY5cDdzM3NxZGJ0cXo2MmFqOGpmdjRzbSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l66xLVIZbm3Y6PGrxj/giphy.gif" width="221" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This can be especially useful if the the render function takes a lot of time and has multiple steps. The user can stay informed of whatever is happening in the background. For example, one could pre-render partial information before the entire data pipeline completes.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;a href="https://www.npmjs.com/package/react-native-gen-ui"&gt;react-native-gen-ui&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://github.com/zerodays/react-native-gen-ui-minimal-example"&gt;minimal example&lt;/a&gt; like the one in this post.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://github.com/zerodays/react-native-gen-ui-weather-example"&gt;complete weather bot example&lt;/a&gt; from the GIF at the top.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This blog post and the &lt;code&gt;react-native-gen-ui&lt;/code&gt; package were made by the awesome team at &lt;a href="https://zerodays.dev/"&gt;zerodays.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>reactnative</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
