<?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: Nishchay</title>
    <description>The latest articles on DEV Community by Nishchay (@kaushiknishchay).</description>
    <link>https://dev.to/kaushiknishchay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4166799%2F1df5499e-35c9-4206-9518-b331fba5581f.png</url>
      <title>DEV Community: Nishchay</title>
      <link>https://dev.to/kaushiknishchay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kaushiknishchay"/>
    <language>en</language>
    <item>
      <title>Migrating OG Image Generation from Satori to Takumi: 30%+ Faster Astro Builds</title>
      <dc:creator>Nishchay</dc:creator>
      <pubDate>Tue, 13 Oct 2026 14:31:59 +0000</pubDate>
      <link>https://dev.to/kaushiknishchay/migrating-og-image-generation-from-satori-to-takumi-30-faster-astro-builds-3a3p</link>
      <guid>https://dev.to/kaushiknishchay/migrating-og-image-generation-from-satori-to-takumi-30-faster-astro-builds-3a3p</guid>
      <description>&lt;p&gt;A few months ago, I wrote about &lt;a href="https://nkaushik.in/writing/automating-social-images-with-satori-and-sharp" rel="noopener noreferrer"&gt;how to automate dynamic social images using Satori and Sharp&lt;/a&gt;. That pipeline gave me consistent, branded 1200×630 Open Graph (OG) cards for every blog post, tag page, and case study at build time without touching manual design tools.&lt;/p&gt;

&lt;p&gt;It worked reliably. But as the number of articles, tag archives, and documentation pages grew past 150 routes, a clear bottleneck emerged: &lt;strong&gt;static build times kept creeping up&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Every full site rebuild was spending dozens of seconds spinning inside V8, computing flexbox layouts with Yoga WASM, and generating SVGs.&lt;/p&gt;

&lt;p&gt;To fix this, I migrated the entire OG image rendering engine from &lt;strong&gt;Vercel's Satori&lt;/strong&gt; to &lt;strong&gt;Takumi (&lt;code&gt;takumi-js&lt;/code&gt;)&lt;/strong&gt;—a high-performance, Rust-native image engine.&lt;/p&gt;

&lt;p&gt;Here is why I made the switch, how the two engines compare under the hood, how to execute the migration with zero layout changes, and the exact real-world benchmark numbers from my Astro build pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Migrate? The Hidden Cost of SSG Social Previews
&lt;/h2&gt;

&lt;p&gt;When you generate social cards at static build time (SSG), each unique route requires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Building a virtual DOM tree (VNode object).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resolving custom fonts, character glyphs, and line wraps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Calculating CSS flexbox layouts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Serializing the layout tree into an SVG string.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rasterizing the SVG into a compressed WebP or JPEG via Sharp.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SSG Build Pipeline:
[Route Metadata] 
       │
       ▼
[Virtual DOM / JSX Spec] 
       │
       ▼
[Layout &amp;amp; Typography Engine] ─── (Satori Yoga WASM vs. Takumi Rust Core)
       │
       ▼
[Rendered SVG Code] 
       │
       ▼
[Sharp Rasterizer] 
       │
       ▼
[Cached 1200x630 JPEG / WebP Asset]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Satori Bottleneck
&lt;/h3&gt;

&lt;p&gt;Satori is an incredible tool that popularized JSX-to-SVG generation. However, it relies on a JavaScript orchestration layer combined with Yoga compiled to WebAssembly. &lt;/p&gt;

&lt;p&gt;When generating over 150 images in a single build pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;V8 Garbage Collection Overhead:&lt;/strong&gt; Constructing thousands of intermediate JS objects for deep JSX trees stresses Node's memory heap.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WASM Boundary Crossing:&lt;/strong&gt; Passing layout constraints and text metrics back and forth across the JS-WASM boundary adds microsecond overhead that compounds over hundreds of runs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Single-Thread Execution:&lt;/strong&gt; While Astro handles concurrency at the route level, heavy CPU work in JavaScript blocks the event loop from resolving file I/O swiftly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something faster that would run natively without requiring a redesign of my existing JSX card templates.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Takumi?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Takumi (&lt;code&gt;takumi-js&lt;/code&gt;)&lt;/strong&gt; is an open-source, ultra-fast image generation engine written in Rust. It provides a drop-in API compatible with Satori, rendering HTML/JSX-like node trees and CSS flexbox styling directly into SVGs and images with native Rust performance.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;GitHub:&lt;/em&gt; &lt;a href="https://github.com/takumi-rs/takumi" rel="noopener noreferrer"&gt;takumi-rs/takumi&lt;/a&gt; | &lt;em&gt;Docs:&lt;/em&gt; &lt;a href="https://takumi.kane.tw/" rel="noopener noreferrer"&gt;takumi.kane.tw&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Takumi is architected from the ground up for speed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Native Rust Core (&lt;code&gt;@takumi-rs/core&lt;/code&gt;):&lt;/strong&gt; Ships precompiled N-API native binaries for Linux (x64, ARM64, musl, glibc), macOS (Apple Silicon &amp;amp; Intel), and Windows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WASM Fallback (&lt;code&gt;@takumi-rs/wasm&lt;/code&gt;):&lt;/strong&gt; If running in edge workers or environments where native binaries cannot load, it falls back seamlessly to WebAssembly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced CSS Support:&lt;/strong&gt; In addition to flexbox, Takumi adds cleaner text wrapping, linear &amp;amp; radial gradients, drop shadows, and rich font shaping out of the box.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Satori vs. Takumi: Architecture Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Metric&lt;/th&gt;
&lt;th&gt;Vercel Satori&lt;/th&gt;
&lt;th&gt;Takumi (&lt;code&gt;takumi-js&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Core Engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TypeScript + Yoga (WASM)&lt;/td&gt;
&lt;td&gt;Pure Rust (&lt;code&gt;@takumi-rs/core&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Execution Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JS runtime + WASM bridge&lt;/td&gt;
&lt;td&gt;Native N-API C++ / Rust bindings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API Signature&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;satori(element, options)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;renderSvg(element, options)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Node Input Schema&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JSX / VNode (&lt;code&gt;{ type, props }&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Identical JSX / VNode schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CSS Capabilities&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Standard flexbox subset&lt;/td&gt;
&lt;td&gt;Flexbox + expanded gradients &amp;amp; shadows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Font Formats&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TTF, OTF, WOFF&lt;/td&gt;
&lt;td&gt;TTF, OTF, WOFF, WOFF2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Footprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Moderate (V8 heap allocations)&lt;/td&gt;
&lt;td&gt;Low (Rust native memory management)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because Takumi was designed with Satori API parity in mind, you do &lt;strong&gt;not&lt;/strong&gt; have to redesign your card layouts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step Migration Guide
&lt;/h2&gt;

&lt;p&gt;Let's walk through the exact code changes made in this repository to migrate from Satori to Takumi.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install Dependencies
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;takumi-js&lt;/code&gt; to your project dependencies:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The package automatically resolves the appropriate native binary for your OS (e.g., &lt;code&gt;@takumi-rs/core-linux-x64-gnu&lt;/code&gt; on Debian/Ubuntu or &lt;code&gt;@takumi-rs/core-darwin-arm64&lt;/code&gt; on Apple Silicon).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add an Engine Switch to Centralized Config
&lt;/h3&gt;

&lt;p&gt;To allow seamless benchmarking and provide a fallback safety net, I added an &lt;code&gt;ogRender&lt;/code&gt; option in &lt;code&gt;src/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="c1"&gt;// src/config.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;siteConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other configuration options&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * OG Image Renderer ('satori' | 'takumi')
   * Controlled via OG_RENDER environment variable
   */&lt;/span&gt;
  &lt;span class="na"&gt;ogRender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OG_RENDER&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;takumi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&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;satori&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;satori&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;takumi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;satori&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;takumi&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="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you toggle between engines instantly using environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;OG_RENDER=takumi npm run build&lt;/code&gt; (Default, ultra-fast)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;OG_RENDER=satori npm run build&lt;/code&gt; (Baseline comparison)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Update the Dynamic OG Endpoint
&lt;/h3&gt;

&lt;p&gt;In Astro, dynamic OG images are generated via an endpoint file like &lt;code&gt;src/pages/og/[...slug].jpg.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the diff showing how simple the migration is:&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="c1"&gt;// src/pages/og/[...slug].jpg.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;satori&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;satori&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;renderSvg&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;takumiRenderSvg&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;takumi-js&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;siteConfig&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;../../config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Your existing VNode / JSX object definition remains 100% identical:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ogElement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&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="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;flexDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;column&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="s1"&gt;space-between&lt;/span&gt;&lt;span class="dl"&gt;'&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="s1"&gt;#0c0f17&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;60px 80px&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;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="c1"&gt;// Title, author avatar, tags, branding...&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;// 2. Options configuration:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&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="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fonts&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fontRegularData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;normal&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fontBoldData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;normal&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;embedFont&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;loadAdditionalAsset&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="na"&gt;code&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="na"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emoji&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getEmojiDataUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Render SVG dynamically based on configured engine:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;siteConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ogRender&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;takumi&lt;/span&gt;&lt;span class="dl"&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;takumiRenderSvg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ogElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;renderOptions&lt;/span&gt;&lt;span class="p"&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;satori&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ogElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;renderOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Rasterize to high-quality progressive JPEG via Sharp:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jpegBuffer&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;sharp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&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="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jpeg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;progressive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Add Comparative Build Scripts to &lt;code&gt;package.json&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Add dedicated benchmark scripts to your &lt;code&gt;package.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;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"astro dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"btakumi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OG_RENDER=takumi npm run build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bsatori"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OG_RENDER=satori npm run build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"astro check &amp;amp;&amp;amp; astro build"&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;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;Now anyone on your team can verify parity or run isolated performance tests with a single command.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Performance Benchmarks
&lt;/h2&gt;

&lt;p&gt;To obtain an accurate, unpolluted baseline, I cleared all local caches (&lt;code&gt;rm -rf og-cache dist .astro&lt;/code&gt;) before each run and executed both builds against the exact same content set (160 total pages, including articles, tags, projects, and index routes).&lt;/p&gt;

&lt;p&gt;Here are the measured numbers:&lt;/p&gt;

&lt;h3&gt;
  
  
  Build Time Comparison (Cold Cache, 160 Routes)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Vercel Satori (&lt;code&gt;npm run bsatori&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Takumi (&lt;code&gt;npm run btakumi&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Astro Page Generation Phase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1m 07.00s (67.0s)&lt;/td&gt;
&lt;td&gt;42.24s&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;37.0% faster&lt;/strong&gt; ⚡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total Wall Clock Time (&lt;code&gt;time&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1m 23.70s (83.7s)&lt;/td&gt;
&lt;td&gt;57.94s&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;30.8% faster&lt;/strong&gt; ⚡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User CPU Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1m 24.45s&lt;/td&gt;
&lt;td&gt;1m 01.73s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26.9% less CPU load&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.94s&lt;/td&gt;
&lt;td&gt;4.49s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24.4% less kernel overhead&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Astro Build Duration (Cold Cache):
Satori: [███████████████████████████████████] 67.0s
Takumi: [██████████████████████] 42.2s  (-37% reduction)

Total Wall Clock Time:
Satori: [████████████████████████████████████████] 83.7s
Takumi: [████████████████████████████] 57.9s  (-30.8% reduction)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why the Difference is Significant
&lt;/h3&gt;

&lt;p&gt;In Static Site Generation (SSG), continuous integration (CI) pipelines on GitHub Actions or Vercel are billed by compute duration. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Faster Feedback Loops:&lt;/strong&gt; Saving 25–30 seconds on every build means pull request previews and production deployments finish in under a minute.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; As the blog grows to 300+ articles, the 37% rendering speed differential prevents CI builds from timing out or hitting memory thresholds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Identical Visual Fidelity:&lt;/strong&gt; The generated SVG and JPEG artifacts match the original layout pixel for pixel with zero visual regression.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Lessons Learned &amp;amp; Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep Dual Renderers During Transition:&lt;/strong&gt; Wrapping the renderer in a site configuration toggle made it effortless to benchmark both engines in CI without committing destructive changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cache Font Buffers in Module Scope:&lt;/strong&gt; Regardless of whether you use Satori or Takumi, load your TTF/WOFF font buffers once at module initialization rather than reading from disk on every endpoint invocation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Invalidate Cache on Renderer Changes:&lt;/strong&gt; Include the active renderer and version tag in your disk cache hash (&lt;code&gt;crypto.createHash('sha256').update(JSON.stringify({ ..., renderer: siteConfig.ogRender, v: '2.0' }))&lt;/code&gt;). This guarantees that switching engines immediately regenerates fresh assets without stale artifacts.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;If your Astro, Next.js, or SvelteKit site generates dynamic social previews at build time, migrating from Satori to Takumi is one of the highest-ROI optimizations you can make. &lt;/p&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Drop-in compatibility:&lt;/strong&gt; Zero layout redesign required.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rust-native speed:&lt;/strong&gt; Over 30% faster total build times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Seamless developer experience:&lt;/strong&gt; Easy configuration toggles and clean integration with Sharp.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try sharing this post on Twitter, LinkedIn, or WhatsApp to see the newly generated Takumi dynamic card in action!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://nkaushik.in/writing/migrating-og-image-generation-from-satori-to-takumi" rel="noopener noreferrer"&gt;nkaushik.in&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>satori</category>
      <category>takumi</category>
      <category>webdev</category>
      <category>ogimage</category>
    </item>
    <item>
      <title>How to Monitor Your Homelab with Beszel and Dozzle</title>
      <dc:creator>Nishchay</dc:creator>
      <pubDate>Tue, 13 Oct 2026 14:30:23 +0000</pubDate>
      <link>https://dev.to/kaushiknishchay/how-to-monitor-your-homelab-with-beszel-and-dozzle-1mbk</link>
      <guid>https://dev.to/kaushiknishchay/how-to-monitor-your-homelab-with-beszel-and-dozzle-1mbk</guid>
      <description>&lt;p&gt;Managing a homelab often starts with a single Docker container and quickly escalates into dozens of self-hosted services. As the footprint grows, keeping tabs on CPU spikes, memory leaks, disk wear, and container logs becomes essential.&lt;/p&gt;

&lt;p&gt;The traditional solution, which usually involves spinning up a heavy monitoring stack like Prometheus, Grafana, Node Exporter, and Loki, often consumes more resources than the homelab services themselves. For a modest server or a collection of low-power nodes, you need something that is fast, lightweight, and simple to configure.&lt;/p&gt;

&lt;p&gt;Beszel and Dozzle make a great alternative.&lt;/p&gt;

&lt;p&gt;Beszel provides real-time system metrics and container resource usage across multiple nodes. Dozzle complements it by offering a web-based, real-time log viewer for all running containers. Together, they form a monitoring setup that runs on less than 50MB of RAM.&lt;/p&gt;

&lt;p&gt;In this guide, we will walk through setting up a clean deployment of both tools on your server, and then show you how to run Beszel on an old Android phone to monitor its resource usage as an auxiliary homelab node.&lt;/p&gt;




&lt;h2&gt;
  
  
  Beszel vs Dozzle: What's the Difference?
&lt;/h2&gt;

&lt;p&gt;While both tools monitor Docker environments, they solve two completely distinct operational needs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Capability&lt;/th&gt;
&lt;th&gt;Beszel (Metrics &amp;amp; Alerts)&lt;/th&gt;
&lt;th&gt;Dozzle (Log Streaming)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Purpose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;System &amp;amp; Container Resource Metrics&lt;/td&gt;
&lt;td&gt;Real-time Container Log Viewer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What It Tracks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CPU, RAM, Disk I/O, Network, GPU, S.M.A.R.T, Docker Stats&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;stdout&lt;/code&gt; &amp;amp; &lt;code&gt;stderr&lt;/code&gt; Docker Container Logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Central Hub + Lightweight Agents across nodes&lt;/td&gt;
&lt;td&gt;Single container connected to &lt;code&gt;/var/run/docker.sock&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Historical Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (Stores long-term trend graphs)&lt;/td&gt;
&lt;td&gt;No (Pure live stream, no database overhead)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alerting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Discord, Telegram, Pushover, Webhooks&lt;/td&gt;
&lt;td&gt;None (focuses solely on log viewing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Footprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~15MB – 25MB RAM&lt;/td&gt;
&lt;td&gt;~10MB – 20MB RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; Use &lt;strong&gt;Beszel&lt;/strong&gt; to find out &lt;em&gt;which&lt;/em&gt; service is hogging memory or when your server is down. Use &lt;strong&gt;Dozzle&lt;/strong&gt; to inspect &lt;em&gt;why&lt;/em&gt; a container crashed by reading its live log output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Beszel and Dozzle?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Beszel is an open-source, lightweight resource monitor. It uses a hub-and-agent architecture. You host one central Hub (which displays the dashboard) and install a tiny Agent on each machine you want to monitor. It tracks CPU, memory, disk, network, GPU usage, temperatures, and per-container resource metrics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dozzle is a dedicated log viewer. It streams Docker container logs directly to your browser. It does not store logs or index them; it simply connects to the Docker socket to provide instant visibility when debugging.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Beszel is a self-hosting must-have
&lt;/h2&gt;

&lt;p&gt;When self-hosting, your hardware resources are usually at a premium. Unlike enterprise setups with dedicated operations budgets, a home server might be an old optiplex, a Raspberry Pi, or a repurposed laptop. You need deep visibility, but you cannot afford to waste 1-2GB of RAM on Prometheus and Grafana.&lt;/p&gt;

&lt;p&gt;Beszel solves this problem by packing three critical monitoring components into an incredibly lightweight footprint. The main home dashboard lists all your systems side-by-side:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmbq8d85lksx039ugsp9t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmbq8d85lksx039ugsp9t.png" alt="The main Beszel homepage shows all the devices currently being monitored." width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Beszel splits metrics into three specific categories:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. System metrics
&lt;/h3&gt;

&lt;p&gt;Beszel tracks long-term system resource trends, including CPU, RAM, network bandwidth, and disk space usage. This lets you view historical spikes and spot pattern changes over days, weeks, or months.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftx173reld9qzo6mc0tco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftx173reld9qzo6mc0tco.png" alt="View detailed system metrics such as CPU usage, memory utilization, and network traffic over time." width="800" height="631"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Container metrics
&lt;/h3&gt;

&lt;p&gt;Instead of running &lt;code&gt;docker stats&lt;/code&gt; in a terminal to see which service is slowing down your system, Beszel auto-detects running containers and charts their individual resource footprint. If a container starts eating memory or pinning the CPU, you will see it immediately.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcnu2289anji55aykhs05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcnu2289anji55aykhs05.png" alt="Beszel charts individual resource utilization profiles for all of your Docker containers." width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. S.M.A.R.T metrics
&lt;/h3&gt;

&lt;p&gt;Hard drive failures are one of the most common causes of data loss in a homelab. Beszel queries S.M.A.R.T database statistics to track temperature, write stats, bad sectors, and overall disk health. It can alert you before a drive completely dies.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F25kd14bpzdm85qs33nks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F25kd14bpzdm85qs33nks.png" alt="Access storage health indicators like S.M.A.R.T data directly inside the dashboard to monitor disk wear." width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Real-time alerts
&lt;/h3&gt;

&lt;p&gt;A monitoring tool is only as good as its notification engine. Beszel lets you configure threshold triggers for critical system metrics. You can set up rules to alert you if a system goes offline, CPU temperature rises too high, memory exceeds a safe range, or disk space runs low. It natively integrates with Discord, Telegram, Pushover, Gotify, and generic webhooks.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgb664qeq0kgmklrzyl7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgb664qeq0kgmklrzyl7a.png" alt="Configure granular notification thresholds for CPU, memory, and temperature metrics directly from the Hub." width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Setting up Beszel
&lt;/h2&gt;

&lt;p&gt;Because Beszel splits visual presentation from metric gathering, we host the central dashboard (the Hub) and the collector (the Agent) on our primary server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic Docker Compose configuration
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;beszel-compose.yml&lt;/code&gt;. This configuration defines both the central hub and the agent that monitors the host server.&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;beszel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;henrygd/beszel:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;beszel&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;9002:8090&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./beszel_data:/beszel_data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PUID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
      &lt;span class="na"&gt;PGID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
      &lt;span class="na"&gt;TZ&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UTC&lt;/span&gt;
      &lt;span class="c1"&gt;# Security: restricts embedding in frames&lt;/span&gt;
      &lt;span class="na"&gt;CSP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frame-ancestors&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'self';"&lt;/span&gt;

  &lt;span class="na"&gt;beszel-agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;henrygd/beszel-agent:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;beszel-agent&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;network_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host&lt;/span&gt;
    &lt;span class="na"&gt;cap_add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CAP_PERFMON&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYS_RAWIO&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYS_ADMIN&lt;/span&gt;
    &lt;span class="c1"&gt;# Expose host hardware and storage info&lt;/span&gt;
    &lt;span class="na"&gt;devices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/dev/dri/card0:/dev/dri/card0"&lt;/span&gt; &lt;span class="c1"&gt;# your graphics card&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/dev/sda:/dev/sda"&lt;/span&gt; &lt;span class="c1"&gt;# your storage device&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/dev/nvme0:/dev/nvme0"&lt;/span&gt; &lt;span class="c1"&gt;# your storage device&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket:ro&lt;/span&gt;
      &lt;span class="c1"&gt;# Add optional path to monitor host storage mounts if needed&lt;/span&gt;
      &lt;span class="c1"&gt;# - /mnt/storage:/extra-filesystems/storage:ro&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4567&lt;/span&gt;
      &lt;span class="na"&gt;KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_AGENT_PUBLIC_KEY_FROM_HUB"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explaining the configuration parameters
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;network_mode: host&lt;/code&gt;: The agent runs directly on the host network stack to reliably fetch system metrics and network statistics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;cap_add&lt;/code&gt; (Capabilities): Providing capabilities like &lt;code&gt;SYS_RAWIO&lt;/code&gt; and &lt;code&gt;CAP_PERFMON&lt;/code&gt; allows the agent to read hardware temperatures and drive health (SMART data) safely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;KEY&lt;/code&gt;: This is the public key generated by the Beszel Hub. When you first launch the Hub dashboard (at &lt;code&gt;http://&amp;lt;your-server-ip&amp;gt;:9002&lt;/code&gt;), create an account, click "Add System", and copy the generated public key into your agent environment file or &lt;code&gt;docker-compose&lt;/code&gt; definition.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  2. Setting up Dozzle
&lt;/h2&gt;

&lt;p&gt;Dozzle runs as a single lightweight container. It only needs access to the read-only Docker socket to read logs from other containers running on the same host.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic Docker Compose configuration
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;dozzle-compose.yml&lt;/code&gt;:&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dozzle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dozzle&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amir20/dozzle:latest&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:8080&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DOZZLE_NO_ANALYTICS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
      &lt;span class="c1"&gt;# Optional: Enable actions (Start/Stop/Restart) directly in the UI&lt;/span&gt;
      &lt;span class="c1"&gt;# DOZZLE_ENABLE_ACTIONS: "true"&lt;/span&gt;
      &lt;span class="c1"&gt;# Optional: Enable basic username/password authentication&lt;/span&gt;
      &lt;span class="c1"&gt;# DOZZLE_AUTH_PROVIDER: "simple"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once running, navigate to &lt;code&gt;http://&amp;lt;your-server-ip&amp;gt;:8080&lt;/code&gt; to access the dashboard. &lt;/p&gt;

&lt;p&gt;Although Dozzle has recently added basic container statistics (like CPU and memory indicators on the home screen), its primary strength lies in its raw log streaming. Beszel is much better suited for system and container resource monitoring since it stores historical data. Keep Dozzle for viewing, filtering, and debugging your container logs in real time.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flixvurnr61kd7d8deblt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flixvurnr61kd7d8deblt.png" alt="Dozzle lists all running containers alongside basic resource footprints on the main page." width="799" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Selecting any container immediately opens a real-time, scrolling log output window, which makes troubleshooting and tracking down runtime exceptions simple.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qkfc4qq2orp8hags256.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qkfc4qq2orp8hags256.png" alt="Filter and search live log streams directly inside your browser window." width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Repurposing an old Android device as a node
&lt;/h2&gt;

&lt;p&gt;One of the best ways to practice self-hosting sustainability is turning old Android phones into lightweight servers. You can run database backups, lightweight script runners, or DNS servers on them.&lt;/p&gt;

&lt;p&gt;Because Beszel is compile-friendly, you can run the static &lt;code&gt;arm64&lt;/code&gt; binary of the agent directly on your Android device to monitor its CPU temperature, battery percentage, RAM, and network usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the agent via Termux
&lt;/h3&gt;

&lt;p&gt;To install the agent on Android, you will use Termux, a terminal emulator and Linux environment app.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Install Termux
&lt;/h4&gt;

&lt;p&gt;Install the latest version of Termux from F-Droid or GitHub. Avoid the outdated version on the Google Play Store.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Download the static ARM64 agent binary
&lt;/h4&gt;

&lt;p&gt;Open Termux on the Android device and update packages. Download the latest compiled static &lt;code&gt;arm64&lt;/code&gt; Linux binary directly from the Beszel GitHub Releases page:&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="c"&gt;# Update package repositories&lt;/span&gt;
pkg update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pkg upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Download the static arm64 agent binary&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; beszel-agent https://github.com/henrygd/beszel/releases/latest/download/beszel-agent-android
&lt;span class="c"&gt;# If you run into compatibility issues, you can download the standard static Linux arm64 binary:&lt;/span&gt;
&lt;span class="c"&gt;# curl -s -L -o beszel-agent https://github.com/henrygd/beszel/releases/latest/download/beszel-agent_Linux_arm64.tar.gz&lt;/span&gt;

&lt;span class="c"&gt;# Make it executable&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x beszel-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Run the agent
&lt;/h4&gt;

&lt;p&gt;To start the agent and hook it up to your server's central Hub, define the incoming port and the public key you generated inside the Hub dashboard:&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="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4567 &lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_HUB_PUBLIC_KEY"&lt;/span&gt; ./beszel-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Root vs. Non-Root&lt;/strong&gt;: Running as a standard user in Termux works for general CPU and memory stats. However, if your Android device is rooted and you want full disk and battery metrics, run the binary as superuser by prefixing the command with &lt;code&gt;su&lt;/code&gt; (using Magisk/KernelSU).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To keep the agent running in the background on Android, you can use &lt;code&gt;tmux&lt;/code&gt;, &lt;code&gt;nohup&lt;/code&gt;, or write a simple Termux service script using Termux-services.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Testing and verification
&lt;/h2&gt;

&lt;p&gt;Once you have set up the containers and Android agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Navigate to your central Beszel Hub at &lt;code&gt;http://&amp;lt;server-ip&amp;gt;:9002&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your primary server (where the agent runs with &lt;code&gt;network_mode: host&lt;/code&gt;) should already show metrics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a new system for the Android device. Input the phone's IP address and port &lt;code&gt;4567&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Within a few seconds, the phone's stats, including CPU spikes and memory consumption, will populate in the dashboard.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://nkaushik.in/writing/monitoring-homelab-with-beszel-and-dozzle" rel="noopener noreferrer"&gt;nkaushik.in&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beszel</category>
      <category>docker</category>
      <category>dozzle</category>
      <category>homelab</category>
    </item>
    <item>
      <title>Programmatic Video with Remotion: Building Videos in React with AI Skills</title>
      <dc:creator>Nishchay</dc:creator>
      <pubDate>Tue, 06 Oct 2026 17:04:50 +0000</pubDate>
      <link>https://dev.to/kaushiknishchay/programmatic-video-with-remotion-building-videos-in-react-with-ai-skills-2hfj</link>
      <guid>https://dev.to/kaushiknishchay/programmatic-video-with-remotion-building-videos-in-react-with-ai-skills-2hfj</guid>
      <description>&lt;p&gt;Every time you finish building a side project, SaaS product, or game, you face the same chore: creating a promo video that looks clean.&lt;/p&gt;

&lt;p&gt;Usually, that means opening Premiere Pro, After Effects, or DaVinci Resolve. You end up nudging keyframes on a timeline, wrestling with GUI export settings, manually lining up sound effects, and repeating the whole cycle whenever you need to fix copy in a 30-second clip. For developers used to code, Git, and component design, the workflow feels slow and disconnected from how we build software.&lt;/p&gt;

&lt;p&gt;Instead of timeline editors, you can build video animations using the tools you already know: React components, JSX, CSS flexbox, and spring physics, then render them directly into MP4 files from your terminal.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Remotion&lt;/strong&gt; fits in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is Remotion?&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remotion&lt;/strong&gt; is an open-source framework that allows developers to create videos programmatically using React, TypeScript/JavaScript, and CSS. Instead of manual timeline editing, video frames are rendered deterministically based on React state and frame numbers, enabling automated video generation, CI/CD rendering, and dynamic personalized video pipelines.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Learn more:&lt;/em&gt; &lt;a href="https://www.remotion.dev/" rel="noopener noreferrer"&gt;Remotion Official Website&lt;/a&gt; | &lt;a href="https://github.com/remotion-dev/remotion" rel="noopener noreferrer"&gt;Remotion GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While building &lt;strong&gt;&lt;a href="https://tambola.nkaushik.in/" rel="noopener noreferrer"&gt;Tambola Live&lt;/a&gt;&lt;/strong&gt;, a real-time WebRTC multiplayer game (covered in my &lt;a href="https://nkaushik.in/projects/online-tambola" rel="noopener noreferrer"&gt;Tambola Architecture Case Study&lt;/a&gt;), I needed a 40-second animated promo trailer. Using Remotion, the official &lt;strong&gt;Remotion AI skills&lt;/strong&gt;, and &lt;strong&gt;Gemini 3.7 Flash&lt;/strong&gt;, I built the entire 1080p video with scene transitions, kinetic typography, and synchronized sound effects in React.&lt;/p&gt;

&lt;p&gt;Here is a breakdown of how programmatic video works, how Remotion's preview studio speeds up iteration, and how AI skills help you generate complete scenes without trial and error.&lt;/p&gt;




&lt;h2&gt;
  
  
  Remotion vs. traditional video editors: why code wins
&lt;/h2&gt;

&lt;p&gt;If you know how to build web apps, you already know most of what it takes to build a Remotion video. Similar to &lt;a href="https://nkaushik.in/writing/automating-social-images-with-satori-and-sharp" rel="noopener noreferrer"&gt;automating social images with Satori and Sharp&lt;/a&gt;, programmatic video replaces manual GUI work with reproducible code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Dimension&lt;/th&gt;
&lt;th&gt;Traditional GUI Editors (Premiere / After Effects)&lt;/th&gt;
&lt;th&gt;Programmatic Video (Remotion)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Workflow Interface&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Proprietary timeline GUI with visual tracks&lt;/td&gt;
&lt;td&gt;React components, JSX, and standard CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Version Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Heavy, binary &lt;code&gt;.prproj&lt;/code&gt; or &lt;code&gt;.aep&lt;/code&gt; files&lt;/td&gt;
&lt;td&gt;Standard Git diffs, pull requests, and code reviews&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Asset Reusability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual copy-pasting across timeline layers&lt;/td&gt;
&lt;td&gt;Composable React components and design tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dynamic Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual re-editing per data variation&lt;/td&gt;
&lt;td&gt;Direct API, JSON, WebSocket, or database inputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Feedback Loop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Background timeline rendering for previews&lt;/td&gt;
&lt;td&gt;Fast browser Studio with instant Hot Module Replacement (HMR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automation &amp;amp; CI/CD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complex desktop rendering macros&lt;/td&gt;
&lt;td&gt;Headless CLI or serverless cloud rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional Video Workflow:
[GUI Timeline] ---&amp;gt; [Manual Keyframes] ---&amp;gt; [Heavy Local Render] ---&amp;gt; [Hard to Update / Automate]

Programmatic Remotion Workflow:
[React &amp;amp; CSS] ---&amp;gt; [Frame &amp;amp; Spring Hooks] ---&amp;gt; [Live Studio Preview] ---&amp;gt; [CLI / Cloud Render]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The core mental model: how Remotion turns React into video
&lt;/h2&gt;

&lt;p&gt;In Remotion, a video is a sequence of still frames rendered at a fixed frame rate (such as 30 or 60 frames per second).&lt;/p&gt;

&lt;p&gt;Instead of relying on browser wall-clock timers like &lt;code&gt;setTimeout&lt;/code&gt; or CSS &lt;code&gt;@keyframes&lt;/code&gt; (which can drift or drop frames during heavy CPU loads), Remotion uses deterministic hooks tied directly to the current frame index:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;useCurrentFrame()&lt;/code&gt;&lt;/strong&gt;: Returns the integer index of the active frame (&lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, ...).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;useVideoConfig()&lt;/code&gt;&lt;/strong&gt;: Provides composition metadata such as &lt;code&gt;fps&lt;/code&gt;, &lt;code&gt;durationInFrames&lt;/code&gt;, &lt;code&gt;width&lt;/code&gt;, and &lt;code&gt;height&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;interpolate()&lt;/code&gt;&lt;/strong&gt;: Maps a frame range to numerical values (for instance, fading opacity from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; between frames 0 and 30).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;spring()&lt;/code&gt;&lt;/strong&gt;: Computes natural, physics-based motion using mass, stiffness, and damping parameters.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A simple 3-second animated title
&lt;/h3&gt;

&lt;p&gt;Here is a minimal Remotion component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="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;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;useCurrentFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useVideoConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interpolate&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;remotion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;IntroTitle&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="nx"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCurrentFrame&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;fps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useVideoConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Calculate smooth physics-based spring entrance&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entrance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;fps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;damping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stiffness&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="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;interpolate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entrance&lt;/span&gt;&lt;span class="p"&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;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.8&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;interpolate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entrance&lt;/span&gt;&lt;span class="p"&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;1&lt;/span&gt;&lt;span class="p"&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;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
      &lt;span class="na"&gt;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;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&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="s1"&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="s1"&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;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#090d16&lt;/span&gt;&lt;span class="dl"&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="s1"&gt;#ffffff&lt;/span&gt;&lt;span class="dl"&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;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`scale(&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="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;opacity&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;
      Tambola Live
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;entrance&lt;/code&gt; depends strictly on &lt;code&gt;frame&lt;/code&gt;, Remotion guarantees pixel-perfect, frame-accurate rendering whether your local machine runs at 10 fps or 120 fps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Speeding up scene generation with Remotion AI skills &amp;amp; Gemini 3.7 Flash
&lt;/h2&gt;

&lt;p&gt;Calculating animation timings, keyframe curves, and scene transitions by hand can take time. To help with this, the Remotion team created official &lt;strong&gt;Agent Skills&lt;/strong&gt; that give coding assistants direct knowledge of Remotion's architecture and APIs.&lt;/p&gt;

&lt;p&gt;You can install Remotion skills into your AI agent environment with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; skills@latest add remotion-dev/skills &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs 12 specialized Remotion skills into your project:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;remotion-best-practices&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enforces frame determinism, avoiding non-deterministic timers, and memory optimizations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;remotion-create&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scaffolds new video projects, compositions, and sequence structures.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;remotion-studio&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Guides interactive preview workflows and debugging via the local studio.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;remotion-render&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Handles headless CLI rendering, codecs (H.264, ProRes, WebM), and output flags.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;remotion-transitions&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Implements &lt;code&gt;@remotion/transitions&lt;/code&gt; (&lt;code&gt;fade&lt;/code&gt;, &lt;code&gt;slide&lt;/code&gt;, &lt;code&gt;wipe&lt;/code&gt;, &lt;code&gt;flip&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;remotion-multimedia&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Orchestrates audio tracks, sound effects synchronization, and video assets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;remotion-captions&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Generates dynamic word-by-word animated subtitles.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Pairing with Gemini 3.7 Flash
&lt;/h3&gt;

&lt;p&gt;With these skills installed, I gave &lt;strong&gt;Gemini 3.7 Flash&lt;/strong&gt; the scene outline for the promo video:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Create a 40-second 1080p promo video for an online WebRTC Tambola game. We need 6 scenes: Hook, Host creating room, Player scanning QR code to join, Live number calling &amp;amp; ticket dabbing, Winning claim fanfare with confetti, and an Outro with a call to action. Use &lt;code&gt;@remotion/transitions&lt;/code&gt; with fade, spring-loaded kinetic typography, and synchronized sound effects."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because the model had direct access to &lt;code&gt;remotion-dev/skills&lt;/code&gt;, it avoided common AI errors like using &lt;code&gt;setInterval&lt;/code&gt; or non-deterministic CSS animations. Instead, it generated clean, modular Remotion sequence components with proper frame calculations and spring curves right away.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fast iteration with Remotion preview studio
&lt;/h2&gt;

&lt;p&gt;The most convenient part of developing with Remotion is the &lt;strong&gt;Remotion Studio&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You start the preview studio with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx remotion studio src/remotion/index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This launches a local preview interface on &lt;code&gt;http://localhost:3000&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interactive Timeline:&lt;/strong&gt; Drag the playhead to scrub across frames smoothly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frame-by-Frame Stepping:&lt;/strong&gt; Use arrow keys to step through individual frames and inspect alignment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Responsive Canvas Zoom:&lt;/strong&gt; Scale from 10% to 200% to verify typography, badges, and mobile mockups.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instant Hot Module Replacement (HMR):&lt;/strong&gt; Update CSS padding, color values, or spring physics in your code editor, and see the canvas update in milliseconds without restarting.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------------------------------------------+
|  Remotion Studio (http://localhost:3000)                          |
+-------------------------------------------------------------------+
|  [ Play / Pause ]  [ Frame: 450 / 1200 ]  [ 30 FPS ]  [ 1080p ]   |
|                                                                   |
|  +-------------------------------------------------------------+  |
|  |                                                             |  |
|  |           [ Animated Ticket &amp;amp; Live Ball Calling ]           |  |
|  |                                                             |  |
|  +-------------------------------------------------------------+  |
|                                                                   |
|  [================== Timeline Scrubbing Bar ===================]  |
|  |-- Scene 1 --|-- Scene 2 --|-- Scene 3 --|-- Scene 4 Gameplay -|  |
+-------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Case study: building the Tambola Live promo video
&lt;/h2&gt;

&lt;p&gt;Here is how the promo video was organized in &lt;code&gt;/src/remotion/&lt;/code&gt; for &lt;a href="https://tambola.nkaushik.in/" rel="noopener noreferrer"&gt;Tambola Live&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Root Composition setup (&lt;code&gt;Root.jsx&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The video root declares the compositions available in the project, defining the resolution, frame rate, and total duration in frames (40 seconds at 30 fps = 1,200 frames):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="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;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;Composition&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;remotion&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;PromoVideo&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;./PromoVideo.jsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Root&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;Composition&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"PromoVideo"&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;PromoVideo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;fps&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1080&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Scene sequencing with transitions (&lt;code&gt;PromoVideo.jsx&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Rather than manually offsetting start frames for every scene, &lt;code&gt;@remotion/transitions&lt;/code&gt; chains scenes together cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="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;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;AbsoluteFill&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;remotion&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;linearTiming&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;@remotion/transitions&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;fade&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;@remotion/transitions/fade&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;AudioManager&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;./audio/AudioManager.jsx&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;Scene1Hook&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;./scenes/Scene1Hook.jsx&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;Scene2Host&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;./scenes/Scene2Host.jsx&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;Scene3Join&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;./scenes/Scene3Join.jsx&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;Scene4Gameplay&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;./scenes/Scene4Gameplay.jsx&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;Scene5Winning&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;./scenes/Scene5Winning.jsx&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;Scene6Outro&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;./scenes/Scene6Outro.jsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PromoVideo&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="nc"&gt;AbsoluteFill&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;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#090d16&lt;/span&gt;&lt;span class="dl"&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="s1"&gt;#ffffff&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="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Synchronized Audio &amp;amp; SFX Layer */&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;AudioManager&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;/* Cinematic Transition Sequences */&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;TransitionSeries&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;155&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;Scene1Hook&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transition&lt;/span&gt;
          &lt;span class="na"&gt;presentation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;fade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;timing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;linearTiming&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;185&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;Scene2Host&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transition&lt;/span&gt;
          &lt;span class="na"&gt;presentation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;fade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;timing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;linearTiming&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;185&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;Scene3Join&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transition&lt;/span&gt;
          &lt;span class="na"&gt;presentation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;fade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;timing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;linearTiming&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;305&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;Scene4Gameplay&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transition&lt;/span&gt;
          &lt;span class="na"&gt;presentation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;fade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;timing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;linearTiming&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;245&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;Scene5Winning&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transition&lt;/span&gt;
          &lt;span class="na"&gt;presentation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;fade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;timing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;linearTiming&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;185&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;Scene6Outro&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;TransitionSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequence&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;TransitionSeries&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;AbsoluteFill&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;h3&gt;
  
  
  3. Adding audio and synthetic sound effects without external files
&lt;/h3&gt;

&lt;p&gt;For the audio layer, we layered background music using Remotion's &lt;code&gt;&amp;lt;Audio&amp;gt;&lt;/code&gt; component alongside &lt;strong&gt;synthetic PCM WAV sound effects generated directly in JavaScript&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="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;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;Audio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;staticFile&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;remotion&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;SOUND_POP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SOUND_DAB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SOUND_WIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SOUND_CHIME&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;./sounds.js&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AudioManager&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;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* 40-Second Upbeat Background Music Track */&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;Audio&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;staticFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;promo-music.wav&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;volume&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="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Synchronized Sound Effects */&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;Sequence&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;20&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;Audio&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;SOUND_CHIME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;volume&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="nc"&gt;Sequence&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;Sequence&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;15&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;Audio&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;SOUND_POP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;volume&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.5&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;Sequence&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;Sequence&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;570&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;15&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;Audio&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;SOUND_DAB&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;volume&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.55&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;Sequence&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;Sequence&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;870&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Audio&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;SOUND_WIN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;volume&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.65&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;Sequence&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By generating data-URI WAV sounds mathematically (sine waves with exponential decay envelopes at 22,050 Hz), sound effects like ball pop sounds, ticket dabs, chimes, and victory fanfares are completely self-contained, requiring zero external asset downloads during rendering.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rendering to production MP4 from the CLI
&lt;/h2&gt;

&lt;p&gt;When you are ready to produce the final video, render it directly from your terminal:&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="c"&gt;# Render high-quality MP4 using H.264 codec&lt;/span&gt;
npx remotion render src/remotion/index.js PromoVideo public/promo-video.mp4 &lt;span class="nt"&gt;--codec&lt;/span&gt; h264
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also extract poster images and video thumbnails at specific frames:&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="c"&gt;# Extract frame 100 as high-resolution poster JPEG&lt;/span&gt;
npx remotion still src/remotion/index.js PromoVideo public/promo-poster.jpg &lt;span class="nt"&gt;--frame&lt;/span&gt; 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For large-scale production (such as generating personalized recap videos for users), you can deploy Remotion to &lt;strong&gt;AWS Lambda&lt;/strong&gt; or &lt;strong&gt;Google Cloud Run&lt;/strong&gt; using &lt;code&gt;@remotion/lambda&lt;/code&gt; to render videos in parallel across distributed serverless functions.&lt;/p&gt;




&lt;h2&gt;
  
  
  See the game and video live in action
&lt;/h2&gt;

&lt;p&gt;The complete promo video generated through this workflow is live today.&lt;/p&gt;

&lt;p&gt;You can visit &lt;strong&gt;&lt;a href="https://tambola.nkaushik.in/" rel="noopener noreferrer"&gt;Tambola Live (tambola.nkaushik.in)&lt;/a&gt;&lt;/strong&gt; to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Play the game:&lt;/strong&gt; Host a room, invite friends via QR code or direct link, roll 1-90 numbers with real-time WebRTC sync, and auto-verify winning tickets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Watch the promo video:&lt;/strong&gt; Scroll down to the &lt;strong&gt;"How To Play"&lt;/strong&gt; section on the homepage to watch the full Remotion-rendered video running in the web player.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code is a practical video tool:&lt;/strong&gt; If you know React and CSS, Remotion gives you full control over animations, typography, and automated rendering.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI skills remove the boilerplate:&lt;/strong&gt; Installing &lt;code&gt;remotion-dev/skills&lt;/code&gt; gives models like Gemini 3.7 Flash the exact context needed to write working sequence timings and spring curves without guesswork.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Studio provides immediate feedback:&lt;/strong&gt; You can inspect frames, test responsive viewports, and tweak CSS without waiting for full exports.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have put off creating a promo video for your project because video editing software feels cumbersome, building it in React with Remotion is worth exploring.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://nkaushik.in/writing/programmatic-video-with-remotion-react-and-ai" rel="noopener noreferrer"&gt;nkaushik.in&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>remotion</category>
      <category>geminiflash</category>
      <category>javascript</category>
      <category>programmaticvideo</category>
    </item>
    <item>
      <title>AdGuard Home: Stop Ads &amp; Trackers for Your Entire Family</title>
      <dc:creator>Nishchay</dc:creator>
      <pubDate>Tue, 06 Oct 2026 17:04:28 +0000</pubDate>
      <link>https://dev.to/kaushiknishchay/adguard-home-stop-ads-trackers-for-your-entire-family-42oi</link>
      <guid>https://dev.to/kaushiknishchay/adguard-home-stop-ads-trackers-for-your-entire-family-42oi</guid>
      <description>&lt;p&gt;How often have you encountered the scenario where you search something and suddenly everywhere you go, you start seeing ads for it. Or, when you try to read a news article and you can barely read anything with all the ads showing up.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvnv2hcr8rlau0z4uniof.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvnv2hcr8rlau0z4uniof.jpg" alt="example of a website filled with full page ads" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although you might use apps like Firefox or extensions such as Adblock, they must be installed on every device, which is often impractical.&lt;/p&gt;

&lt;p&gt;I faced a similar issue because my family, like many others, isn't tech-savvy. I needed a solution that could be implemented to shield them from ads and malware without requiring any changes on their part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter, AdGuard Home
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AdguardTeam/AdGuardHome" rel="noopener noreferrer"&gt;AdGuard Home&lt;/a&gt; is an open-source, network-wide DNS server that blocks ads, telemetry, and tracking domains before they ever reach your smartphones, laptops, smart TVs, or IoT devices.&lt;/p&gt;

&lt;p&gt;In this guide, we'll cover how to install AdGuard Home, configure the &lt;strong&gt;best 2026 blocklists&lt;/strong&gt;, optimize upstream DNS settings, and deploy it across your entire home network.&lt;/p&gt;

&lt;p&gt;🤖&lt;/p&gt;

&lt;p&gt;Let's assume we're working in a Linux environment (such as a Raspberry Pi, Debian mini PC, or Docker container), as it simplifies 24/7 reliability. However, the installation and web interface steps are identical across platforms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;* *&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Head over to the &lt;a href="https://github.com/AdguardTeam/AdGuardHome?tab=readme-ov-file#automated-install-linux-and-mac" rel="noopener noreferrer"&gt;AdGuard Home Automated Install&lt;/a&gt;. Installing AdGuard is as simple as running a single command.&lt;/p&gt;

&lt;p&gt;To install with &lt;code&gt;curl&lt;/code&gt; run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To install with &lt;code&gt;wget&lt;/code&gt; run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget &lt;span class="nt"&gt;--no-verbose&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; - https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs the AdGuard Home service, which will act as your local recursive/forwarding DNS server.&lt;/p&gt;

&lt;h4&gt;
  
  
  What is DNS?
&lt;/h4&gt;

&lt;p&gt;DNS stands for &lt;strong&gt;Domain Name System&lt;/strong&gt;. In simple terms, it is the internet's address book. Every website you visit has an underlying IP address (e.g. &lt;code&gt;66.11.184.216&lt;/code&gt;). When your browser asks for &lt;code&gt;google.com&lt;/code&gt;, DNS translates that human-friendly name into the server's IP address. By acting as your DNS server, AdGuard Home inspects domain requests and instantly drops requests to known advertising, tracker, and phishing domains.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting Up AdGuard Home &amp;amp; Web Interface
&lt;/h2&gt;

&lt;p&gt;After installation, the setup wizard starts on port &lt;code&gt;3000&lt;/code&gt;. Open your browser and navigate to &lt;code&gt;http://&amp;lt;your-server-ip&amp;gt;:3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Click &lt;strong&gt;Get Started&lt;/strong&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85ckudm4soczsrgcmst1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85ckudm4soczsrgcmst1.png" alt="AdGuard Home Get Started page" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Configure Admin Web Interface &amp;amp; DNS Port.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;By default, the admin web UI listens on port &lt;code&gt;80&lt;/code&gt;. If you already run a web server (like Nginx or Traefik), change the admin port to &lt;code&gt;3000&lt;/code&gt; or &lt;code&gt;8080&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep the &lt;strong&gt;DNS server port&lt;/strong&gt; on &lt;code&gt;53&lt;/code&gt; (standard DNS port).&lt;/p&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3fdnqvb7pwouvqecx1kj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3fdnqvb7pwouvqecx1kj.png" alt="AdGuard Home network port configuration page" width="800" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Set your admin username and password.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyeplrb5bq2n848tb0vst.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyeplrb5bq2n848tb0vst.png" alt="AdGuard Home admin credentials setup" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the wizard completes, log into the dashboard at &lt;code&gt;http://&amp;lt;your-server-ip&amp;gt;:3000&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best AdGuard Home Blocklists (2026 Recommended)
&lt;/h2&gt;

&lt;p&gt;To add blocklists, go to &lt;strong&gt;Filters → DNS blocklists → Add blocklist → Add a custom list&lt;/strong&gt; in your AdGuard Home dashboard. &lt;/p&gt;

&lt;p&gt;Here is our curated, tested stack of blocklists that provides maximum protection with zero false positives (safe for family use without breaking online banking or streaming):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Blocklist Name&lt;/th&gt;
&lt;th&gt;Focus Category&lt;/th&gt;
&lt;th&gt;Recommended Source URL&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AdGuard DNS Filter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ads &amp;amp; Trackers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Core ad blocking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OISD Basic / Big&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Universal Ad &amp;amp; Telemetry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://big.oisd.nl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Zero false positives (Family-safe)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Peter Lowe's List&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ad &amp;amp; Tracking Servers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus&amp;amp;showintro=0&amp;amp;mimetype=plaintext&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High performance &amp;amp; lightweight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Steven Black Unified Hosts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Malware + Adware&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Malicious domain protection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Phishing URL Blocklist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Phishing &amp;amp; Scams&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://adguardteam.github.io/HostlistsRegistry/assets/filter_30.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Security &amp;amp; Anti-Phishing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AdGuard Popup Hosts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Annoying Popups&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Notification &amp;amp; popup spam&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9oxwgbhbagyw1dernnr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9oxwgbhbagyw1dernnr.png" alt="AdGuard Home DNS blocklist settings" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Recommended DNS Settings &amp;amp; Upstream DNS
&lt;/h2&gt;

&lt;p&gt;To maximize speed and prevent ISP snooping, navigate to &lt;strong&gt;Settings → DNS Settings&lt;/strong&gt; and configure encrypted upstream DNS providers (DoH / DoT):&lt;/p&gt;

&lt;h3&gt;
  
  
  Upstream DNS Servers (Copy &amp;amp; Paste)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://dns.quad9.net/dns-query
https://cloudflare-dns.com/dns-query
https://dns.adguard-dns.com/dns-query
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Optimal DNS Settings:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Upstream mode:&lt;/strong&gt; &lt;em&gt;Parallel requests&lt;/em&gt; (queries all upstream servers concurrently and uses the fastest response).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DNS cache size:&lt;/strong&gt; &lt;code&gt;67108864&lt;/code&gt; (64 MB cache for instant sub-millisecond local responses).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimistic caching:&lt;/strong&gt; Enable (serves cached responses while refreshing stale entries in background).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Can AdGuard Home Block YouTube Ads in 2026? (The Truth &amp;amp; Workarounds)
&lt;/h2&gt;

&lt;p&gt;One of the most frequent questions homelabbers ask is: &lt;em&gt;"Can I block YouTube ads network-wide using AdGuard Home?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer: No, not via DNS alone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; Google serves YouTube video advertisements from the exact same content delivery networks (CDNs) and domain names (e.g., &lt;code&gt;*.googlevideo.com&lt;/code&gt;) as the real video you are trying to watch. If AdGuard Home blocks that domain, the YouTube video will simply fail to buffer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Effective Workarounds for YouTube Ads:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desktop / Mobile Browser:&lt;/strong&gt; Use &lt;strong&gt;uBlock Origin&lt;/strong&gt; or the &lt;strong&gt;Brave Browser&lt;/strong&gt;, which perform deep cosmetic and request filtering inside the browser DOM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Android &amp;amp; Android TV:&lt;/strong&gt; Use open-source third-party clients like &lt;strong&gt;SmartTube&lt;/strong&gt; (for Android TV) or &lt;strong&gt;ReVanced&lt;/strong&gt; (for Android smartphones).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parental Controls on AdGuard:&lt;/strong&gt; While it cannot block YouTube ads, AdGuard Home &lt;em&gt;can&lt;/em&gt; enforce &lt;strong&gt;YouTube SafeSearch&lt;/strong&gt; or schedule full blocking of YouTube during study hours under &lt;strong&gt;Filters → Blocked Services&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Deploying AdGuard Home to All Devices (Router Level)
&lt;/h2&gt;

&lt;p&gt;To protect your entire house without configuring every phone, tablet, and TV individually, set AdGuard Home as your home router's primary DNS server.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Log into your Wi-Fi router's admin panel (typically &lt;code&gt;192.168.1.1&lt;/code&gt; or &lt;code&gt;192.168.0.1&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate to &lt;strong&gt;DHCP Settings / LAN Settings / DNS&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set &lt;strong&gt;Primary DNS Server&lt;/strong&gt; to your AdGuard Home IP address (e.g. &lt;code&gt;192.168.1.150&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set &lt;strong&gt;Secondary DNS Server&lt;/strong&gt; to your backup DNS or leave blank.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save and reboot your router.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F317zyeg1z6wplg046eih.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F317zyeg1z6wplg046eih.jpg" alt="TP-Link Router DHCP DNS settings example" width="800" height="1566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once refreshed, every phone, laptop, and smart appliance connecting to your Wi-Fi will automatically query AdGuard Home, blocking ads and tracking seamlessly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Parental Controls &amp;amp; Service Scheduling
&lt;/h2&gt;

&lt;p&gt;AdGuard Home includes built-in service blocking under &lt;strong&gt;Filters → Blocked services&lt;/strong&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5u1kp5vvbhzbph83idg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5u1kp5vvbhzbph83idg.png" alt="Adguard Home block services eg., youtube, tiktok" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can toggle one-click restrictions on specific platforms (TikTok, Roblox, Discord, adult domains) or schedule service downtime for bedtime:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0mx83j7smlyuasxgow69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0mx83j7smlyuasxgow69.png" alt="Adguard home schedule service blocking" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💬 Frequently Asked Questions (FAQ)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are the best AdGuard Home blocklists to use in 2026?
&lt;/h3&gt;

&lt;p&gt;The ideal blocklist stack includes &lt;strong&gt;AdGuard DNS Filter&lt;/strong&gt; (for general ads), &lt;strong&gt;OISD Basic/Big&lt;/strong&gt; (near-zero false positives, family-friendly), &lt;strong&gt;Steven Black's Unified Hosts&lt;/strong&gt; (malware and adware), and &lt;strong&gt;Peter Lowe's List&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can AdGuard Home block YouTube ads?
&lt;/h3&gt;

&lt;p&gt;No, DNS blockers cannot block YouTube video ads because the ads and the video streams originate from identical domain names (&lt;code&gt;*.googlevideo.com&lt;/code&gt;). Use browser extensions like &lt;strong&gt;uBlock Origin&lt;/strong&gt; or client apps like &lt;strong&gt;SmartTube&lt;/strong&gt; on Android TV.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the best upstream DNS servers for AdGuard Home?
&lt;/h3&gt;

&lt;p&gt;Use encrypted DNS-over-HTTPS (DoH) providers such as &lt;strong&gt;Quad9&lt;/strong&gt; (&lt;code&gt;https://dns.quad9.net/dns-query&lt;/code&gt;) for malware filtering and &lt;strong&gt;Cloudflare&lt;/strong&gt; (&lt;code&gt;https://cloudflare-dns.com/dns-query&lt;/code&gt;) for speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does AdGuard Home slow down internet connection speeds?
&lt;/h3&gt;

&lt;p&gt;No. In fact, AdGuard Home accelerates browsing by caching frequent DNS queries locally and blocking heavy tracking scripts and media ads from ever downloading.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related Homelab &amp;amp; Self-Hosting Guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/securing-your-homelab-with-crowdsec-traefik-and-docker" rel="noopener noreferrer"&gt;Securing My Homelab: Integrating CrowdSec, Traefik, and Ntfy in Docker&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/monitoring-homelab-with-beszel-and-dozzle" rel="noopener noreferrer"&gt;How to Monitor Your Homelab with Beszel and Dozzle&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/replace-google-photos-self-host-immich-in-2026" rel="noopener noreferrer"&gt;Replace Google Photos: Self-Host Immich in 2026&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/getting-started-with-self-hosting-in-2026" rel="noopener noreferrer"&gt;Getting Started with Self-Hosting in 2026&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://nkaushik.in/writing/adguard-home-for-families-how-to-stop-ads-and-trackers-without-installing-extensions" rel="noopener noreferrer"&gt;nkaushik.in&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>adblock</category>
      <category>adguard</category>
      <category>adguardhome</category>
      <category>ads</category>
    </item>
    <item>
      <title>Top 4 ways to Run LLM locally on Android and iOS</title>
      <dc:creator>Nishchay</dc:creator>
      <pubDate>Tue, 06 Oct 2026 16:29:29 +0000</pubDate>
      <link>https://dev.to/kaushiknishchay/top-4-ways-to-run-llm-locally-on-android-and-ios-539c</link>
      <guid>https://dev.to/kaushiknishchay/top-4-ways-to-run-llm-locally-on-android-and-ios-539c</guid>
      <description>&lt;p&gt;In my previous blog, I explored the technical rabbit hole of running Llama.cpp in Termux on an old Android phone. While that was a rewarding experiment, let's be honest: it wasn't exactly "plug-and-play" for most people.&lt;/p&gt;

&lt;p&gt;But it's 2026, and the game has changed. You no longer need to be a Linux wizard to have a private, powerful AI in your pocket. In this guide, I'm covering the &lt;strong&gt;Top 4 ways&lt;/strong&gt; you can run LLMs locally on your Android or iOS device—completely for free, with &lt;strong&gt;unlimited requests&lt;/strong&gt;, and &lt;strong&gt;zero data leaving your phone&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Comparison Matrix (2026 Tested)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;App / Method&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;RAM Required&lt;/th&gt;
&lt;th&gt;Supported Formats&lt;/th&gt;
&lt;th&gt;Offline Mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Edge Gallery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One-click setup &amp;amp; NPU speed&lt;/td&gt;
&lt;td&gt;Android / iOS&lt;/td&gt;
&lt;td&gt;2GB - 4GB&lt;/td&gt;
&lt;td&gt;LiteRT / Gemma 4 / Gemma 3&lt;/td&gt;
&lt;td&gt;✅ 100% Offline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PocketPal AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Daily chat &amp;amp; Uncensored models&lt;/td&gt;
&lt;td&gt;Android / iOS&lt;/td&gt;
&lt;td&gt;4GB - 8GB&lt;/td&gt;
&lt;td&gt;GGUF (llama.cpp)&lt;/td&gt;
&lt;td&gt;✅ 100% Offline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AnythingLLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PDF &amp;amp; Document RAG&lt;/td&gt;
&lt;td&gt;Android / iOS&lt;/td&gt;
&lt;td&gt;6GB - 12GB&lt;/td&gt;
&lt;td&gt;GGUF / Ollama API / Cloud&lt;/td&gt;
&lt;td&gt;✅ 100% Offline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Termux + Ollama&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Power users &amp;amp; CLI automation&lt;/td&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;4GB - 8GB&lt;/td&gt;
&lt;td&gt;Ollama Library / GGUF&lt;/td&gt;
&lt;td&gt;✅ 100% Offline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  1. Google AI Edge Gallery: The "One-Click" Starter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/google-ai-edge/gallery" rel="noopener noreferrer"&gt;https://github.com/google-ai-edge/gallery&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google's official showcase is the perfect starting point. It's the easiest way to see what your phone is actually capable of. It's optimized for mobile NPUs, meaning it's fast and efficient for tasks like tone refinement, audio transcription, and image understanding.&lt;/p&gt;

&lt;p&gt;The latest version now supports &lt;strong&gt;Gemma 4&lt;/strong&gt; and &lt;strong&gt;Gemma 3&lt;/strong&gt;, models specifically "distilled" to run on mobile hardware without eating up your RAM. For a dedicated deep-dive on setting up Gemma 4 with Edge Gallery, check out my &lt;a href="https://nkaushik.in/writing/running-gemma-4-locally-on-ios-android-ai-edge-gallery" rel="noopener noreferrer"&gt;Running Gemma 4 Locally on iOS &amp;amp; Android Guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "Skills" Update:&lt;/strong&gt; One of the coolest additions is support for &lt;strong&gt;SKILLS&lt;/strong&gt;. You can now extend what the AI can do by pulling from a vast library at &lt;a href="https://skills.sh/" rel="noopener noreferrer"&gt;skills.sh&lt;/a&gt;. My personal favorite is the &lt;a href="https://skills.sh/blader/humanizer/humanizer" rel="noopener noreferrer"&gt;Humanizer&lt;/a&gt; skill—it's great for making AI-generated text sound more, well, human.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://youtube.com/shorts/1H6z2-K7gvw" rel="noopener noreferrer"&gt;https://youtube.com/shorts/1H6z2-K7gvw&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Models Available
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Gemma 4 E2B and E4B&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gemma 3 1B&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gemma 3n E2B and E4B&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DeepSeek R1 Distill Qwen 1.5B&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Qwen 2.5 1.5B&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;LiteRT distilled models&lt;/p&gt;&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%2Fcdn.hashnode.com%2Fuploads%2Fcovers%2F6984dc9e559eb2d777b15a66%2F1043f69c-870f-4a48-84fc-405b37014bdc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fuploads%2Fcovers%2F6984dc9e559eb2d777b15a66%2F1043f69c-870f-4a48-84fc-405b37014bdc.png" alt="List of models available in google edge gallery app" width="800" height="4476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Bundling your own model
&lt;/h4&gt;

&lt;p&gt;If you're feeling adventurous, you can even bundle your own models by following the &lt;a href="https://huggingface.co/litert-community" rel="noopener noreferrer"&gt;LiteRT community guide&lt;/a&gt;. It walks you through converting models from Pytorch or other formats.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. PocketPal AI (Best for General Chat &amp;amp; Uncensored Models)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/a-ghorbani/pocketpal-ai" rel="noopener noreferrer"&gt;PocketPal AI&lt;/a&gt; is the one I keep coming back to. It's built on &lt;strong&gt;llama.cpp&lt;/strong&gt; (the industry standard), but the interface is actually designed for a human using a phone, not a developer staring at a terminal.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;'Pals' feature&lt;/strong&gt; is the standout here. Instead of one messy chat thread, you can set up different "Pals" with their own system prompts. I have one for coding and another for creative writing. It also handles the latest architectures like &lt;strong&gt;Qwen 3.5&lt;/strong&gt; and &lt;strong&gt;LFM 2.5&lt;/strong&gt;, which are incredibly fast on mobile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works for me:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy Model Setup:&lt;/strong&gt; You can download GGUF models directly within the app. For a smooth experience, I recommend any GGUF model (try &lt;strong&gt;Unsloth's&lt;/strong&gt; uploads) with &lt;strong&gt;Q4_K_M quantization&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;100% Private:&lt;/strong&gt; Once the model is on your phone, you can go into Airplane Mode and the AI won't even notice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Live Stats:&lt;/strong&gt; It shows you tokens per second in real-time. It's satisfying to see your phone's hardware in action.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Running Uncensored Local LLMs on Android with PocketPal
&lt;/h3&gt;

&lt;p&gt;If you need a local model without restrictive alignment filters for creative writing or raw coding analysis, PocketPal AI makes loading uncensored GGUFs simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open PocketPal AI → Navigate to the &lt;strong&gt;Models&lt;/strong&gt; tab.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tap &lt;strong&gt;Add Custom Model (Hugging Face URL)&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Paste a direct GGUF link for an uncensored model (such as &lt;code&gt;Dolphin-2.9.4-Llama3.1-8B-GGUF&lt;/code&gt; in &lt;code&gt;Q4_K_M&lt;/code&gt; quantization or &lt;code&gt;Qwen2.5-1.5B-Instruct-GGUF&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PocketPal downloads the GGUF weights directly to internal storage and runs them completely sandboxed with 0 guardrails.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. AnythingLLM: The Researcher's Workspace
&lt;/h2&gt;

&lt;p&gt;If you need your AI to actually &lt;em&gt;do&lt;/em&gt; something with your files, &lt;a href="https://anythingllm.com/mobile" rel="noopener noreferrer"&gt;AnythingLLM&lt;/a&gt; is a different beast. It's less of a chatbot and more of a portable workspace.&lt;/p&gt;

&lt;p&gt;The killer feature is &lt;strong&gt;On-Device RAG&lt;/strong&gt;. You can feed it a PDF or text file sitting on your phone, and the AI will answer questions based &lt;strong&gt;only&lt;/strong&gt; on that document. I've used this to summarize 50-page technical docs during flights with zero internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What sets it apart:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chat with your docs:&lt;/strong&gt; Local indexing means your data never touches a server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tools &amp;amp; Agents:&lt;/strong&gt; It supports web scraping, calendar editing, and the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;, so it's slowly becoming a true mobile agent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The "Infinite Power" Trick:&lt;/strong&gt; If your phone is struggling, you can connect it to a massive 70B model running on your home PC via API and use your phone as a remote window into that power.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Termux + Ollama: The Power User Shortcut
&lt;/h2&gt;

&lt;p&gt;This is for my Android users who miss the command line. We're using &lt;a href="https://termux.dev/" rel="noopener noreferrer"&gt;Termux&lt;/a&gt; to run &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; directly on the device. It's the closest you'll get to a desktop experience on a mobile screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Quick Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install Termux (use the version from F-Droid, not the Play Store).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;pkg install ollama&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Type &lt;code&gt;ollama run qwen2.5:0.5b&lt;/code&gt; or &lt;code&gt;ollama run qwen3.5:0.8b&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;For the full setup guide, check out my companion tutorials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/install-ollama-in-termux-android-2026-guide" rel="noopener noreferrer"&gt;How to Install and Run Ollama in Termux on Android (2026 Guide)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/how-to-install-llamacpp-windows-mac-linux-termux" rel="noopener noreferrer"&gt;How to Install llama.cpp on Windows, Mac, Linux, and Termux (2026)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/running-24-7-local-ai-on-an-old-android-without-overheating" rel="noopener noreferrer"&gt;Running 24/7 Local AI on an Old Android without Overheating&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. Ollama will auto-download the model and drop you into a CLI chat. You can run pretty much anything from the &lt;a href="https://ollama.com/search" rel="noopener noreferrer"&gt;Ollama Library&lt;/a&gt; as long as your RAM can handle it. It's the fastest way to test new models the second they drop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary: Which one should you pick?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;RAM &amp;amp; Hardware Specs&lt;/th&gt;
&lt;th&gt;Supported Models &amp;amp; Classes&lt;/th&gt;
&lt;th&gt;Ease of Use&lt;/th&gt;
&lt;th&gt;Customization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Edge Gallery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Beginners &amp;amp; NPU Acceleration&lt;/td&gt;
&lt;td&gt;2GB - 4GB RAM (Tensor / Snapdragon NPUs like Pixel 8/9, Galaxy S24)&lt;/td&gt;
&lt;td&gt;Gemma 3 1B/4B, Gemma 4, LiteRT distilled models&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PocketPal AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Daily Chats &amp;amp; Uncensored Models&lt;/td&gt;
&lt;td&gt;4GB - 8GB RAM (OnePlus 11/12, Galaxy S23/S24, iPhone 15/16)&lt;/td&gt;
&lt;td&gt;Qwen 3.5, LFM 2.5, Llama 3.2 (1B/3B GGUF)&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AnythingLLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Document RAG &amp;amp; Local Workspaces&lt;/td&gt;
&lt;td&gt;6GB - 12GB RAM (Galaxy S24 Ultra, iPhone 16 Pro Max)&lt;/td&gt;
&lt;td&gt;Custom Local GGUF, Ollama endpoints, Cloud APIs&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Termux + Ollama&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developers &amp;amp; Command Line Users&lt;/td&gt;
&lt;td&gt;4GB - 8GB RAM (64-bit ARM devices on Android 10+)&lt;/td&gt;
&lt;td&gt;Qwen 2.5/3.5 (0.8B/1.5B), Llama 3.2 1B/3B&lt;/td&gt;
&lt;td&gt;⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Related Local AI Guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/install-ollama-in-termux-android-2026-guide" rel="noopener noreferrer"&gt;How to Install and Run Ollama in Termux on Android (2026 Guide)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/how-to-install-llamacpp-windows-mac-linux-termux" rel="noopener noreferrer"&gt;How to Install llama.cpp on Windows, Mac, Linux, and Termux (2026)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/running-gemma-4-locally-on-ios-android-ai-edge-gallery" rel="noopener noreferrer"&gt;Running Gemma 4 Locally on iOS and Android: The Google AI Edge Gallery Guide&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/running-24-7-local-ai-on-an-old-android-without-overheating" rel="noopener noreferrer"&gt;Running 24/7 Local AI on an Old Android without Overheating&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nkaushik.in/writing/how-to-run-llm-models-on-old-android-devices-locally" rel="noopener noreferrer"&gt;How to Run LLM Models on Old Android Devices Locally&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Happy (Local) Chatting!&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://nkaushik.in/posts/top-4-ways-to-run-llm-locally-on-android-and-ios" rel="noopener noreferrer"&gt;nkaushik.in&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>android</category>
      <category>localllm</category>
      <category>gemma4</category>
    </item>
    <item>
      <title>Building Web ScreenShare using WebRTC</title>
      <dc:creator>Nishchay</dc:creator>
      <pubDate>Tue, 06 Oct 2026 16:15:30 +0000</pubDate>
      <link>https://dev.to/kaushiknishchay/building-web-screenshare-using-webrtc-1e3g</link>
      <guid>https://dev.to/kaushiknishchay/building-web-screenshare-using-webrtc-1e3g</guid>
      <description>&lt;p&gt;The web is a very powerful platform it grants you the ability to share something with everyone irrespective of what device they are using, be it an Android or iOS phone or a Windows, Linux, or Mac laptop.&lt;/p&gt;

&lt;p&gt;With time the things, a browser could do have also increased greatly (except IE11, switch to Firefox already). A webpage is no longer about some text and images, you can add a lot more power-hungry features to your webpage. You can create Games, Build Services like Google Docs, Browser Stack, and a lot more all running on a browser.&lt;/p&gt;

&lt;p&gt;This post is going to talk about one of the browser’s features I had a chance to work with and the tech I explored for integrating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem Statement
&lt;/h3&gt;

&lt;p&gt;The problem statement I had in front of me was to implement a screen-share feature where-in when a User is sharing the screen it would be possible for others to view it over the internet as a real-time video.&lt;/p&gt;

&lt;p&gt;The first step was to break down this problem into smaller problems and build a prototype around it.  &lt;/p&gt;

&lt;p&gt;As I saw it, the problem had two pieces, first figuring out how to capture the screen, second, how to send it across to other users as a live video feed.  &lt;/p&gt;

&lt;p&gt;I’ll start with the Screen-share bit first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screen-share/Record
&lt;/h3&gt;

&lt;p&gt;I knew websites already do it (you would’ve seen it in action on Zoom or Google Meet) but wasn’t aware How easy it is to do it on our own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDisplayMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above line is all it takes to get started with screen-sharing. It is supported by all the latest browsers and you can read more on its support at &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you run the above code snippet you will get a &lt;code&gt;Promise&amp;lt;MediaStream&amp;gt;&lt;/code&gt;, now it is up-to us to decide how we want to use this.&lt;/p&gt;

&lt;p&gt;We can either share it with other people using WebRTC (more on it later) or even record it and download the video.&lt;/p&gt;

&lt;p&gt;Example snippet to download the recorded video (taken from &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&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;var&lt;/span&gt; &lt;span class="nx"&gt;recordedChunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

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

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video/webm; codecs=vp8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;mediaRecorder&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;MediaRecorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ondataavailable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handleDataAvailable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleDataAvailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-available&lt;/span&gt;&lt;span class="dl"&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;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;span class="nx"&gt;recordedChunks&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recordedChunks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&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;function&lt;/span&gt; &lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;blob&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;Blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recordedChunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video/webm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;display: none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test.webm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&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;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// demo: to download after 9sec&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;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stopping&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;9000&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;One thing to note about the above &lt;code&gt;getDisplayMedia&lt;/code&gt; API is it behaves differently on browsers. For example, Chrome lets you share between a single tab/window/desktop, Firefox shares the whole window and Safari shares the entire desktop.&lt;/p&gt;

&lt;p&gt;A limitation this API has is, it won’t let you share only a part of your webpage.&lt;/p&gt;

&lt;p&gt;Now that we have solved half of the problem, let’s get to the second part.&lt;/p&gt;

&lt;h3&gt;
  
  
  WebRTC
&lt;/h3&gt;

&lt;p&gt;If you haven’t heard about it, don’t worry, I will add resources for you to read. It is an interesting topic so you should give it a try.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WebRTC&lt;/strong&gt; (Web Real-Time Communication) is a technology that enables Web applications and sites to capture and optionally stream audio and/or video media, as well as to exchange arbitrary data between browsers without requiring an intermediary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The above definition is taken from MDN docs which captures the gist of what WebRTC is (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;WebRTC is a very detailed topic and it won’t be easy to cover all that it provides in this article. I’d highly recommend going through this video on YouTube.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=p2HzZkd2A40" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=p2HzZkd2A4&lt;/a&gt;0&lt;/p&gt;

&lt;p&gt;Based on the above video, we derive two important steps, we need to acquire a video/audio stream and then use WebRTC to communicate it with peers.&lt;/p&gt;

&lt;p&gt;This video stream that we share can either be a stream from your webcam or any MediaStream from other sources.&lt;/p&gt;

&lt;p&gt;If you recall, the screen-share stream we acquired is also a MediaStream which can be used with WebRTC.&lt;/p&gt;

&lt;p&gt;So once we have set-up our WebRTC connection we can simply publish this Screen-share stream on it and make it available to peers in real-time.&lt;/p&gt;

&lt;p&gt;Implementing WebRTC from scratch using the basic API can be a tedious task, there are various libraries and frameworks available for use. These libraries hides away the software complexity, but to ensure WebRTC works for 100% of the users you would also need to setup a server of your own.&lt;/p&gt;

&lt;p&gt;PeerJS (&lt;a href="https://peerjs.com/" rel="noopener noreferrer"&gt;https://peerjs.com&lt;/a&gt;) is one such example, which makes it easier for you to get started.&lt;/p&gt;

&lt;p&gt;Various companies provide you with the infrastructure and SDK needed to get started with real-time video, for example, Twilio. This takes away both the software and hardware complexity and makes it easier for you to implement and support a wide variety of users and devices.&lt;/p&gt;

&lt;p&gt;In my case, we were already utilizing Twilio for the video call feature and it also let us publish more MediaStream if needed for a user.&lt;/p&gt;

&lt;p&gt;This made sharing screen-share with other users a breeze, as all it required was to acquire the screen-share stream and then publish it on the connection for a user.&lt;/p&gt;

&lt;p&gt;Twilio also has a sample app that you can refer to for understanding how to do the above.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/twilio/twilio-video-app-react" rel="noopener noreferrer"&gt;https://github.com/twilio/twilio-video-app-react&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;This was just one example of what can be achieved using these 2 pieces of technologies: WebRTC &amp;amp; getDisplayMedia.&lt;/p&gt;

&lt;p&gt;The possibilities are limitless.&lt;/p&gt;

&lt;p&gt;For example, You can make a piece of software which records your webcam as well as your screen to make a coding tutorial with few lines of code right from your browser.&lt;/p&gt;

&lt;p&gt;A small experiment I did was to find a way to only share a part of webpage. For this I wrapped the area which I wanted to share with a distinctive color border and on other users before showing the video stream I simply used image processing to crop the pieces outside of the border. This isn’t a very great solution and is extremely memory intensive to do it in JavaScript so I won’t recommend it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Special shout-out to &lt;a href="https://medium.com/u/372f5561ad9b" rel="noopener noreferrer"&gt;Punit Gupta&lt;/a&gt; for helping me with this article.&lt;/p&gt;

&lt;p&gt;Thanks for reading.  &lt;/p&gt;

&lt;p&gt;Do share your thoughts and suggestions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://nkaushik.in/writing/exploring-the-power-of-web-browsers-in-2020" rel="noopener noreferrer"&gt;nkaushik.in&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>streaming</category>
      <category>webrtc</category>
    </item>
    <item>
      <title>Hello World!</title>
      <dc:creator>Nishchay</dc:creator>
      <pubDate>Tue, 06 Oct 2026 15:50:21 +0000</pubDate>
      <link>https://dev.to/kaushiknishchay/hello-world-d4c</link>
      <guid>https://dev.to/kaushiknishchay/hello-world-d4c</guid>
      <description>&lt;p&gt;Hi there👋, I’m Nishchay, an Engineer, I am currently exploring the world of AI and Self Hosting. This is my space to share my thoughts with you.&lt;/p&gt;

&lt;p&gt;I have over ten years of experience in the software industry, where I have worked with various organizations in both B2C and B2B sectors.&lt;/p&gt;

&lt;p&gt;Over the years, I have worked with many technologies and tools that fascinated me. I began my journey with PHP and have since built Native Android Apps and React Native Apps. I've also had the chance to work with Ruby on Rails, Golang, and Python. Additionally, I have hands-on experience in maintaining services and infrastructure on both Google Cloud (GCP) and AWS, as well as with Kubernetes (k8s).&lt;/p&gt;

&lt;p&gt;I currently work in the JavaScript/TypeScript ecosystem, specializing in React.JS &amp;amp; Node.js.&lt;/p&gt;

&lt;p&gt;I possess extensive expertise in the frontend domain, which I recently leveraged to build a comprehensive Performance Testing platform from the ground up. This project encompassed not only the frontend but also the backend and the data engineering aspects. I undertook this ambitious endeavor with a very small team, yet we successfully implemented the entire platform. It is now widely used throughout the organization and serves as a crucial guardrail for performance-related checks across the entire company. The platform's integration has significantly enhanced our ability to monitor and optimize performance, ensuring that all our systems operate efficiently and effectively. This achievement has not only streamlined our processes but also reinforced the importance of performance testing within our organizational framework.&lt;/p&gt;

&lt;p&gt;I enjoy building things and trying out new ideas. I've been working to keep up with the rapid advancements in the Generative AI space.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://nkaushik.in/posts/hello-world" rel="noopener noreferrer"&gt;nkaushik.in&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>generativeai</category>
      <category>helloworld</category>
      <category>javascript</category>
      <category>programmingblogs</category>
    </item>
  </channel>
</rss>
