<?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: Magenta Qin</title>
    <description>The latest articles on DEV Community by Magenta Qin (@magentaqin).</description>
    <link>https://dev.to/magentaqin</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%2F947502%2F8536a55d-e61b-4b99-a2d0-5de7cea922a0.jpg</url>
      <title>DEV Community: Magenta Qin</title>
      <link>https://dev.to/magentaqin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/magentaqin"/>
    <language>en</language>
    <item>
      <title>Loop Engineering Visualized: A World Cup Fan Journey with SerpApi and Codex</title>
      <dc:creator>Magenta Qin</dc:creator>
      <pubDate>Wed, 29 Jul 2026 18:50:38 +0000</pubDate>
      <link>https://dev.to/serpapi/loop-engineering-visualized-a-world-cup-fan-journey-with-serpapi-and-codex-2p7d</link>
      <guid>https://dev.to/serpapi/loop-engineering-visualized-a-world-cup-fan-journey-with-serpapi-and-codex-2p7d</guid>
      <description>&lt;p&gt;Prompt Engineering focuses on shaping a model call, while Loop Engineering focuses on what happens after that call: how an agent observes the result, updates its state, chooses the next action, and decides whether to continue.&lt;/p&gt;

&lt;p&gt;The difference sounds simple. But it is hard to feel until you build something where the model has to keep going:&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%2Fljjfingq43ch2diz9n70.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%2Fljjfingq43ch2diz9n70.png" alt="A conceptual Loop Engineering loop, from trigger and state to tools, observations, memory updates, and the stop condition." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A conceptual Loop Engineering loop, from trigger and state to tools, observations, memory updates, and the stop condition.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The easiest way to understand a loop is to watch one unfold.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I built a small World Cup fan demo with Codex and SerpApi to make Loop Engineering visible.&lt;/p&gt;

&lt;p&gt;The product idea is intentionally simple: a fan starts with one match, and the agent follows the journey from there. It identifies an interesting player, discovers related matches, searches for highlights, reads fan debates, and keeps deciding what to explore next.&lt;/p&gt;

&lt;p&gt;Eventually, it produces a trace showing how the entire journey unfolded. But under the surface, this is not just a football app. &lt;strong&gt;The fan journey is the agent loop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every discovery becomes a new context. Every action changes the state. And every result influences what the agent does next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Demo
&lt;/h2&gt;

&lt;p&gt;The demo is called &lt;strong&gt;World Cup Fan Journey&lt;/strong&gt; , and the full source code is available in the &lt;a href="https://github.com/serpapi/world-cup-loop-engineering" rel="noopener noreferrer"&gt;Github repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F312ip78mwmqv544i3ggo.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%2F312ip78mwmqv544i3ggo.png" alt="The World Cup Fan Journey starts with a natural-language match request and turns it into an interactive path through matches, players, highlights, and fan discussions." width="799" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The World Cup Fan Journey starts with a natural-language match request and turns it into an interactive path through matches, players, highlights, and fan discussions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The journey starts with a simple natural-language request:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Show me France vs Morocco&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or in Chinese:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;我想看挪威对法国的比赛&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The app interprets the request, identifies the football matchup, and initializes the first journey state. From there, the agent starts building a fan pack around the match:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live match context and upcoming games&lt;/li&gt;
&lt;li&gt;Highlights and social content&lt;/li&gt;
&lt;li&gt;Fan debates and trending stories&lt;/li&gt;
&lt;li&gt;Related players and AI-generated match analysis&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%2F5wsf160o6hk8d8cw6thy.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%2F5wsf160o6hk8d8cw6thy.png" alt="The first iteration turns a match request into a fan pack grounded in live sports data, videos, news, trends, and AI-generated analysis." width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The first iteration turns a match request into a fan pack grounded in live sports data, videos, news, trends, and AI-generated analysis.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The important interaction is what happens after the user clicks a player.&lt;/p&gt;

&lt;p&gt;For example, if the user follows Kylian Mbappé, the goal changes. The app no longer needs to explain only France vs Morocco. It now needs to explore Mbappé’s football trail: recent matches, videos, debates, memes, and related players.&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%2Fhfasjnq4piw2zedjwrs3.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%2Fhfasjnq4piw2zedjwrs3.png" alt="Following a player starts a new iteration: the goal shifts from exploring one match to building that player’s wider football trail." width="799" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Following a player starts a new iteration: the goal shifts from exploring one match to building that player’s wider football trail.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The player clicks to update the goal. From there, the agent enters another loop: it searches, observes the results, updates the journey state, and decides what to retrieve next.&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%2F7j9l2z52je3lkq47uwyo.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%2F7j9l2z52je3lkq47uwyo.png" alt="Following Mbappé changes the goal and expands the journey into three new match trails, each with fresh content and new players the user can follow into the next iteration." width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Following Mbappé changes the goal and expands the journey into three new match trails, each with fresh content and new players the user can follow into the next iteration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The user chooses the direction. The loop handles the exploration.&lt;/p&gt;

&lt;p&gt;Finally, when the user clicks &lt;strong&gt;Generate my football graph&lt;/strong&gt; , the demo stops exploring and turns the accumulated journey into a &lt;strong&gt;Loop Engineering trace&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That final output is not meant to be a normal social graph. It is meant to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What did the agent know, what did it observe, what did it do next, and why?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Pipeline vs Loop
&lt;/h2&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%2Frrvo1xm233y3yjw39sgo.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%2Frrvo1xm233y3yjw39sgo.png" alt="A pipeline follows a predefined sequence, while this demo loops through goal, planning, search, observation, and state updates until the user chooses the next direction." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A pipeline follows a predefined sequence, while this demo loops through goal, planning, search, observation, and state updates until the user chooses the next direction.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A normal pipeline follows a predefined path:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fetch the match → fetch videos → fetch players → render the page.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The next step is already known. However, this demo works differently.&lt;/p&gt;

&lt;p&gt;The agent starts with a goal and builds a journey state around it. Search results introduce new matches, debates, and related players. These observations expand the state and create new directions the journey can take.&lt;/p&gt;

&lt;p&gt;If the user follows Mbappé, the goal changes. The agent plans which of Mbappé's matches to explore, retrieves related football content, and adds new players and observations to the journey. The user can then follow another player, changing the goal again.&lt;/p&gt;

&lt;p&gt;The loop continues:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Goal → Plan → Search → Observe → Update State → Choose Next Direction → Goal Changes → Plan Again&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a human-in-the-loop agent loop. The agent handles planning and exploration, while the user decides which direction is worth following.&lt;/p&gt;

&lt;p&gt;A pipeline says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do A, then B, then C.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This loop asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Given the current goal and journey state, what should we explore next?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Mapping the Demo to the Loop
&lt;/h2&gt;

&lt;p&gt;The easiest way to map the demo to the loop is to follow it iteration by iteration.&lt;/p&gt;

&lt;p&gt;Each iteration starts with a trigger and a goal. The agent plans what to explore, calls tools, observes the results, and updates the journey state.&lt;/p&gt;

&lt;p&gt;In this demo, the next iteration begins when the user chooses a new direction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Trigger → Goal → Plan → Tools → Observe → Update State → Next Trigger&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&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%2F69nxsouj8kn7pb1ucy3e.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%2F69nxsouj8kn7pb1ucy3e.png" alt="The demo unfolds as a human-in-the-loop journey: each user choice changes the goal and starts another iteration, while the same loop structure repeats underneath." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The demo unfolds as a human-in-the-loop journey: each user choice changes the goal and starts another iteration, while the same loop structure repeats underneath.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Iteration 1: Start with a Match
&lt;/h3&gt;

&lt;p&gt;The first trigger is the user's search:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Show me France vs Morocco.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Build a fan pack for France vs Morocco.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The app parses the request and uses sports, YouTube, news, trends, and player data to assemble the first fan journey.&lt;/p&gt;

&lt;p&gt;The resulting observations include the match status, score, available videos, discussions, and related players. Those results become the current journey state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At the end of the first iteration, the system has not just rendered a page. It has created possible directions for the next iteration.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Iteration 2: Following Mbappé Changes the Goal
&lt;/h3&gt;

&lt;p&gt;The user clicks &lt;strong&gt;Follow Kylian Mbappé&lt;/strong&gt;. That click becomes the next trigger. In the code, this action starts a new planning step:&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%2Flx1qsn78o37z7fa4eq7l.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%2Flx1qsn78o37z7fa4eq7l.png" alt="The followPlayer() flow turns a user click into a new plan, enriches the selected matches, and expands the current journey state." width="799" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The followPlayer() flow turns a user click into a new plan, enriches the selected matches, and expands the current journey state.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The LLM decides which three recent or trending matches are worth exploring:&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%2F011c7lk0fot5jm5ygcwx.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%2F011c7lk0fot5jm5ygcwx.png" alt="planPlayerExpansion() asks the LLM to select three recent or trending matches for the followed player and return them as a structured exploration plan." width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;planPlayerExpansion() asks the LLM to select three recent or trending matches for the followed player and return them as a structured exploration plan.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The plan is then grounded with external search data by SerpApi. It retrieves the surrounding sports results, videos, news, and trends.&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%2Fxvdk6x93frgeqzkogemv.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%2Fxvdk6x93frgeqzkogemv.png" alt="enrichPlannedMatch() grounds the LLM’s plan with sports results, videos, and discussions from SerpApi before adding the new match trail to the journey state." width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;enrichPlannedMatch() grounds the LLM’s plan with sports results, videos, and discussions from SerpApi before adding the new match trail to the journey state.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The agent observes three new match trails and discovers more related players. The state expands. Now the user has another set of possible directions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The user chooses the direction. The agent explores it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Iteration 3: The Loop Continues
&lt;/h3&gt;

&lt;p&gt;Suppose the user follows Erling Haaland.&lt;/p&gt;

&lt;p&gt;The same loop structure runs again, but with a different goal and a different state.&lt;/p&gt;

&lt;p&gt;The agent plans Haaland's match trail, retrieves new context, observes the results, and adds new match and player nodes to the journey.&lt;/p&gt;

&lt;p&gt;This is why the demo is not a fixed pipeline. The structure repeats, but &lt;strong&gt;the content of each iteration depends on the current goal and accumulated state.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Iteration 4: Another Direction, Same Loop
&lt;/h3&gt;

&lt;p&gt;The user follows Martin Odegaard.&lt;/p&gt;

&lt;p&gt;Again:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;New trigger → new goal → new plan → new observations → expanded state&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The loop structure stays stable. The journey does not.&lt;/p&gt;

&lt;p&gt;By this point, the system has accumulated multiple player trails, discovered matches, and possible next actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Iteration: Stop and Generate the Trace
&lt;/h3&gt;

&lt;p&gt;The user clicks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Generate my football graph.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This becomes the stop condition.&lt;/p&gt;

&lt;p&gt;The agent stops exploring and turns the accumulated journey into a Loop Engineering trace.&lt;/p&gt;

&lt;p&gt;The final output shows how each iteration changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the goal&lt;/li&gt;
&lt;li&gt;the current state&lt;/li&gt;
&lt;li&gt;the tools used&lt;/li&gt;
&lt;li&gt;the observations&lt;/li&gt;
&lt;li&gt;the memory carried forward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The graph is therefore not just a record of which players the user followed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;It is a visualization of how the loop changed from one iteration to the next.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fxnci8e5sm5007jbp6zyu.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%2Fxnci8e5sm5007jbp6zyu.png" alt="The final trace reveals how each user action changed the goal, expanded the state, and started the next iteration." width="799" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The final trace reveals how each user action changed the goal, expanded the state, and started the next iteration.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SerpApi is Useful
&lt;/h2&gt;

&lt;p&gt;For this demo, SerpApi acts as the observation layer. The LLM can plan what to explore, but the football world keeps changing: match status and scores change, new videos appear, and news and fan discussions evolve.&lt;/p&gt;

&lt;p&gt;The loop needs a way to observe that external world.&lt;/p&gt;

&lt;p&gt;SerpApi provides that context through several search APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://serpapi.com/google-sports-api" rel="noopener noreferrer"&gt;&lt;strong&gt;Google Sports API&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;:&lt;/strong&gt; match scores, status, stage, venue, and time&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://serpapi.com/youtube-search-api" rel="noopener noreferrer"&gt;&lt;strong&gt;YouTube Search API&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;:&lt;/strong&gt; highlights and shorts&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://serpapi.com/google-news-api" rel="noopener noreferrer"&gt;&lt;strong&gt;Google News API&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;a href="https://serpapi.com/google-trends-api" rel="noopener noreferrer"&gt;&lt;strong&gt;Google Trends API&lt;/strong&gt;&lt;/a&gt;: debates, narratives, and rising topics&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://serpapi.com/instagram-profile-api" rel="noopener noreferrer"&gt;&lt;strong&gt;Instagram Profile API&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;:&lt;/strong&gt; player profile context&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://serpapi.com/google-images-api" rel="noopener noreferrer"&gt;&lt;strong&gt;Google Images API&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;:&lt;/strong&gt; player photos and visual fallbacks&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The LLM decides what to explore. SerpApi gives the loop fresh context from the outside world.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Building It with Codex
&lt;/h2&gt;

&lt;p&gt;I built this demo with Codex, but the hardest part was not writing the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At the beginning, even the product goal was vague.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I knew I wanted to build a World Cup demo that made Loop Engineering visible. I did not yet know what that experience should look like.&lt;/p&gt;

&lt;p&gt;The first version reflected that uncertainty.&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%2Fjp2j48msm2igjpxqqmu1.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjp2j48msm2igjpxqqmu1.jpeg" alt="The first version exposed the loop as an engineering dashboard, making the architecture visible but the user experience difficult to understand." width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The first version exposed the loop as an engineering dashboard, making the architecture visible but the user experience difficult to understand.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It exposed almost every engineering concept directly: loop state, observations, memory, constraints, tool calls, validators, and a replay timeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technically, the ideas were there. But as a product, it was painful to use.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A user had to understand Loop Engineering before they could understand the demo. The interface was explaining the architecture instead of letting the user experience it.&lt;/p&gt;

&lt;p&gt;So the development process became iterative.&lt;/p&gt;

&lt;p&gt;I would inspect the UI, explain what felt confusing, and ask Codex to change the product around that observation. Codex would read the existing codebase, make a targeted change, run the build, and give me another version to react to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Build → Observe → Rethink the goal → Patch → Verify → Repeat&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After several iterations, the product idea became much clearer.&lt;/p&gt;

&lt;p&gt;Instead of showing the loop as an engineering dashboard, the demo would hide most of the machinery behind a simple fan journey:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Start with a match. Follow a player. Discover another match. Follow another player.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The user experiences the loop first.&lt;/p&gt;

&lt;p&gt;Only at the end does the app reveal the Loop Engineering trace and show how each action changed the goal, state, observations, and next iteration.&lt;/p&gt;

&lt;p&gt;That shift changed the entire interface.&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%2Fxmkkl7w8esg5v7gktw7z.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%2Fxmkkl7w8esg5v7gktw7z.png" alt="The redesigned interface hides the engineering machinery behind a simple fan journey, letting users experience the loop before revealing the trace underneath it." width="799" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The redesigned interface hides the engineering machinery behind a simple fan journey, letting users experience the loop before revealing the trace underneath it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The interesting part is that Codex was not simply implementing a finished specification. The specification itself became clearer through the implementation loop.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Every version gave me a new observation. Those observations changed what I asked Codex to build next.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The workflow itself became loop-shaped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Failures That Made the Loop Clearer
&lt;/h2&gt;

&lt;p&gt;The most useful lessons came from the parts that did not work. Some were ordinary UI bugs. Others exposed weaknesses in how I was thinking about the loop itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The First Graph Showed Relationships, Not Iterations
&lt;/h3&gt;

&lt;p&gt;My first version ended with a graph of players and matches. It showed that Mbappé, Haaland, and Ødegaard were connected to different matches, but it did not explain why the journey moved from one player to the next.&lt;/p&gt;

&lt;p&gt;It showed relationships, but not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What triggered each iteration&lt;/li&gt;
&lt;li&gt;How the goal changed&lt;/li&gt;
&lt;li&gt;What the agent observed&lt;/li&gt;
&lt;li&gt;What state was carried forward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was a relationship graph, not a loop trace.&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%2Fuxpt063qxyij7gtlfe9a.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%2Fuxpt063qxyij7gtlfe9a.png" width="800" height="721"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I redesigned the final output around iterations. The new trace shows the goal, current state, tools, observations, and memory for each step.&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%2F2t49rf3mc8qbcslvmnvq.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%2F2t49rf3mc8qbcslvmnvq.png" alt="The redesigned trace organizes the journey by iteration, revealing how each trigger changed the goal, state, observations, and memory carried into the next step." width="800" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The redesigned trace organizes the journey by iteration, revealing how each trigger changed the goal, state, observations, and memory carried into the next step.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A visualization should explain the concept, not just decorate the data.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This change also clarified the product itself. The fan journey was only the surface. The real output was the sequence of state transitions underneath it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Search Relevance Is Not Factual Correctness
&lt;/h3&gt;

&lt;p&gt;At one point, a video title implied that Mbappé had scored in a match where he had not.&lt;/p&gt;

&lt;p&gt;The player-trail search included his name:&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="nf"&gt;findYoutubeVideo&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;planned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&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;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; shorts memes`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;short&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That made sense for retrieval. The goal was to find content related to Mbappé. But a relevant title could still imply incorrect facts, such as the wrong score or a goal he never scored.&lt;/p&gt;

&lt;p&gt;The bug was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The application treated search-derived copy as verified sports data.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I added a deterministic fact guard before displaying those titles. Sports data verifies the score and match events; search and the LLM only shape how the content is presented.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Sports data owns the facts. Search and the LLM shape the narrative.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The broader Loop Engineering lesson is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A relevant observation is not always trustworthy enough to update factual state.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What I Would Improve Next
&lt;/h2&gt;

&lt;p&gt;The demo makes the loop visible, but it is still a prototype. If I wanted to harden it, I would focus on three things.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Unify the Loop State Model
&lt;/h3&gt;

&lt;p&gt;The demo currently has two state models because the product evolved in stages.&lt;/p&gt;

&lt;p&gt;The earlier replay-mode prototype derives a display snapshot from &lt;code&gt;SessionState&lt;/code&gt; and &lt;code&gt;PersistentMemory&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildLoopStateSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final fan journey uses &lt;code&gt;RabbitHoleState&lt;/code&gt; as its main application state:&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RabbitHoleState&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This split reflects the development history: the replay dashboard came first, and the fan journey came later.&lt;/p&gt;

&lt;p&gt;If I hardened the project, I would unify both flows around one sequence of loop-state transitions. The fan UI, execution logic, and final trace should all read from the same underlying history instead of adapting between separate representations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The trace should come from the loop that actually ran, not from a parallel model built to explain it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That would make each iteration easier to inspect, persist, and replay.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add Production Guardrails
&lt;/h3&gt;

&lt;p&gt;The demo mostly relies on the user to decide when to stop.&lt;/p&gt;

&lt;p&gt;A production loop should also enforce operational limits, including maximum iterations, API timeouts, retry budgets, and graceful fallback behavior when a tool fails.&lt;/p&gt;

&lt;p&gt;Instead of retrying forever, the loop should be able to stop and return the best partial result available.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A stop condition is also a reliability boundary.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Persist the Journey Across Sessions
&lt;/h3&gt;

&lt;p&gt;Right now, the fan journey is mainly session-based.&lt;/p&gt;

&lt;p&gt;I would persist followed players, expanded trails, accepted content, and other useful memory so that a new session does not start from zero. That would let the loop carry context across a longer fan journey, rather than only across interactions on one page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Main Takeaway
&lt;/h2&gt;

&lt;p&gt;Everyone is talking about Loop Engineering right now, but people do not always mean exactly the same thing. Some use the term to describe the iteration inside a single AI agent: observe, update state, choose the next action, and repeat. Others use it at a higher level to describe systems that coordinate multiple agent runs over time.&lt;/p&gt;

&lt;p&gt;Neither view is necessarily wrong. They are looking at the same idea from different levels of abstraction.&lt;/p&gt;

&lt;p&gt;In this demo, I focused on the &lt;strong&gt;micro view&lt;/strong&gt;. The loop is human-in-the-loop: the user chooses a direction, the agent explores it, new observations expand the state, and the next user action starts another iteration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The hard part is not making the model act once. It is designing how goals, observations, state, and decisions evolve from one iteration to the next.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was also why football turned out to be such a useful example. The outside world keeps changing. A match moves from upcoming to live to full-time. Scores change. New videos appear. News and fan discussions evolve. The model cannot simply remember that world correctly. The loop has to be observed again.&lt;/p&gt;

&lt;p&gt;For this demo, SerpApi's &lt;a href="https://serpapi.com/google-sports-api" rel="noopener noreferrer"&gt;&lt;strong&gt;Google Sports API&lt;/strong&gt;&lt;/a&gt;became the main source of match state: scores, match status, teams, schedules, and other sports context. That fresh data could then be written back into the loop state before the next iteration continued.&lt;/p&gt;

&lt;p&gt;Calling a tool is not enough. The result has to become a new observation, and that observation has to update the state the next iteration will use.&lt;/p&gt;

&lt;p&gt;If you are building an AI agent or workflow around live sports data, try the SerpApi Google Sports API and see what kind of loop you can build around changing match state.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I Built a Star Wars Grogu Product Research Agent with Codex, Lark, and SerpApi</title>
      <dc:creator>Magenta Qin</dc:creator>
      <pubDate>Tue, 28 Jul 2026 09:42:36 +0000</pubDate>
      <link>https://dev.to/serpapi/how-i-built-a-star-wars-grogu-product-research-agent-with-codex-lark-and-serpapi-lmg</link>
      <guid>https://dev.to/serpapi/how-i-built-a-star-wars-grogu-product-research-agent-with-codex-lark-and-serpapi-lmg</guid>
      <description>&lt;p&gt;Cross-border e-commerce sellers often spend hours comparing the same products across different Amazon marketplaces. Prices, reviews, and seller signals vary by country, but the process is still largely manual.&lt;/p&gt;

&lt;p&gt;I wanted to see how far I could automate it with a small AI agent built using Codex, SerpApi, and Lark.&lt;/p&gt;

&lt;p&gt;Suppose I ask in Codex:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compare Grogu products in the US and Japan&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The workflow is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;search Amazon marketplaces&lt;/li&gt;
&lt;li&gt;fetch product details&lt;/li&gt;
&lt;li&gt;summarize seller signals&lt;/li&gt;
&lt;li&gt;send a report to Lark&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I kept the stack intentionally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python 3.12&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;uv&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pydantic&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SerpApi Python SDK&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAI structured outputs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lark custom bot webhook&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex&lt;/strong&gt; for both development and as the conversational interface&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Running the Agent from Codex&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once everything was wired together, I could simply ask Codex:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compare Grogu products in the US and Japan&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Codex understood the request, triggered the CLI workflow, collected marketplace data via SerpApi, and delivered a structured report to Lark.&lt;/p&gt;

&lt;p&gt;Here’s the entire flow in action:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;0:00&lt;br&gt;
/0:16&lt;br&gt;
1×&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;The overall flow looks like this:&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%2Fgy109zua3alr0bd1tmuv.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%2Fgy109zua3alr0bd1tmuv.png" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Starting with a natural-language request in Codex, the agent first validates that the question is related to cross-border Amazon research.&lt;/p&gt;

&lt;p&gt;OpenAI then translates the request into a structured command. SerpApi handles both product discovery and detailed product retrieval, while OpenAI extracts seller-focused insights from the collected data.&lt;/p&gt;

&lt;p&gt;Finally, the results are packaged into a Lark card and delivered via a Custom Bot webhook.&lt;/p&gt;

&lt;p&gt;I intentionally did not start with FastAPI, background jobs, or a database. For the MVP, the important question was simpler: Can I go from a natural-language product question to a useful cross-border product card?&lt;/p&gt;

&lt;h2&gt;
  
  
  Translating Natural Language into Commands
&lt;/h2&gt;

&lt;p&gt;The natural-language entrypoint uses OpenAI to translate user requests into structured commands.&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%2Fb2z2mq9p1ckeopv993fj.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%2Fb2z2mq9p1ckeopv993fj.png" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compare Grogu products in the US and Japan&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;becomes:&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;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Grogu"&lt;/span&gt; 
&lt;span class="nv"&gt;marketplaces&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s2"&gt;"us"&lt;/span&gt;,&lt;span class="s2"&gt;"jp"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; 
&lt;span class="nv"&gt;output_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"send_lark"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using a strict schema keeps the pipeline predictable and much easier to debug. Instead of letting the model orchestrate everything, I only ask it to generate structured commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching Amazon with SerpApi
&lt;/h2&gt;

&lt;p&gt;This project relies on two SerpApi endpoints.&lt;/p&gt;

&lt;p&gt;The Amazon Search API is used to discover candidate products, while the Amazon Product API enriches them with much richer details.&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%2Fg0mkr0i9nwzl08s5pdeg.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%2Fg0mkr0i9nwzl08s5pdeg.png" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Search results provide ranking context and thumbnails, while product pages contain detailed information such as images, availability, and descriptions.&lt;/p&gt;

&lt;p&gt;Combining both produced much better product cards than using either endpoint alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modeling Product Data
&lt;/h2&gt;

&lt;p&gt;Amazon pages are messy.&lt;/p&gt;

&lt;p&gt;Some products have ratings but no availability.&lt;/p&gt;

&lt;p&gt;Some have images but no variants.&lt;/p&gt;

&lt;p&gt;Some fields simply don't exist.&lt;/p&gt;

&lt;p&gt;Missing data is normal, not an exception.&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%2Fk63xzwpj5zy05lksqhr7.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%2Fk63xzwpj5zy05lksqhr7.png" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My first instinct was to say, "Why not model most fields as optional?" That is true, but it is not the whole solution.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The real issue was that SerpApi returns useful product data from several different places.&lt;/strong&gt; Some fields are flat. Some fields are nested. Some fields have different names depending on whether they came from Amazon Search API or Amazon Product API. Some products should not be shown at all if they are missing the signals a seller actually needs.&lt;/p&gt;

&lt;p&gt;To make the data usable, I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;normalized both endpoints into a common &lt;code&gt;Product&lt;/code&gt; model;&lt;/li&gt;
&lt;li&gt;filtered out products with weak seller signals;&lt;/li&gt;
&lt;li&gt;merged Product API details back into search results;&lt;/li&gt;
&lt;li&gt;treated missing fields as expected rather than failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;Product&lt;/code&gt; model still uses optional fields, because missing data is normal:&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%2F7on39v7aohn00snze6b4.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%2F7on39v7aohn00snze6b4.png" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But optional fields alone were not enough. I also filtered search results before choosing products for detail lookup:&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%2Fol0lnzkt5qmxiikaym56.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%2Fol0lnzkt5qmxiikaym56.png" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was important for the Lark card. A cross-border seller does not want a table full of &lt;code&gt;Rating: N/A&lt;/code&gt; and &lt;code&gt;Reviews: N/A&lt;/code&gt;. Those rows make the card noisy and less actionable.&lt;br&gt;&lt;br&gt;
The next issue was nested and inconsistent JSON.&lt;br&gt;&lt;br&gt;
For price, SerpApi may return a string, a number-like value, or a nested object:&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%2Fb2j2sfm0qblqgpovxk2q.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%2Fb2j2sfm0qblqgpovxk2q.png" width="799" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For availability, the Product API does not always use one stable field. I had to check &lt;code&gt;availability&lt;/code&gt;, &lt;code&gt;stock&lt;/code&gt;, and sometimes &lt;code&gt;delivery&lt;/code&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%2Folqa25sibiv1x4e1n13t.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%2Folqa25sibiv1x4e1n13t.png" width="799" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the Agent Narrow
&lt;/h2&gt;

&lt;p&gt;The most interesting part of the project isn't Grogu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's scope.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The entrypoint only supports cross-border Amazon product research.&lt;/p&gt;

&lt;p&gt;If somebody asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is today's weather?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;the app simply rejects the request.&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%2Ffpvc3b9rfdebsu1cp5i9.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%2Ffpvc3b9rfdebsu1cp5i9.png" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Requests outside the project scope are rejected locally before calling OpenAI.&lt;/p&gt;

&lt;p&gt;For supported requests, OpenAI returns a strict command object:&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%2Fdu5fi54k96r6jhf3ocl7.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%2Fdu5fi54k96r6jhf3ocl7.png" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This keeps the agent predictable. It translates requests into commands instead of improvising actions.&lt;/p&gt;

&lt;p&gt;I deliberately avoided turning this into a general chatbot.The agent only knows one workflow: cross-border Amazon product research.That narrow scope makes the behavior easier to explain, test, and trust. It also keeps OpenAI API responsible for translation rather than improvisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating Seller-Friendly Insights and Delivering Them to Lark
&lt;/h2&gt;

&lt;p&gt;Once product data has been collected, OpenAI generates seller-focused insights. I intentionally constrain the model to use only information returned by the APIs, avoiding hallucinated prices, ratings, or availability.&lt;/p&gt;

&lt;p&gt;Instead of producing generic summaries, the analysis focuses on demand signals, social proof, pricing, and obvious risks — information that is much more useful for sellers.&lt;/p&gt;

&lt;p&gt;The final result is delivered through a Lark Custom Bot webhook.&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%2Frkbdwwxy2tm6mewsa0w4.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%2Frkbdwwxy2tm6mewsa0w4.png" width="799" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Handling
&lt;/h2&gt;

&lt;p&gt;External APIs fail. That's normal.&lt;/p&gt;

&lt;p&gt;I treated each layer independently.&lt;/p&gt;

&lt;p&gt;If OpenAI fails, no command is generated.&lt;/p&gt;

&lt;p&gt;If SerpApi fails, the analysis stops with a clear message.&lt;/p&gt;

&lt;p&gt;If Lark delivery fails, the report can still be viewed locally.&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%2Fqg7mcfl78g6rsuxlcrds.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%2Fqg7mcfl78g6rsuxlcrds.png" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This separation keeps failures localized and prevents one component from bringing down the entire workflow.&lt;/p&gt;

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

&lt;p&gt;The most interesting lesson from this project wasn’t the Grogu theme or the Lark card.&lt;/p&gt;

&lt;p&gt;It was learning where the boundaries should be.&lt;/p&gt;

&lt;p&gt;The agent works because it stays narrow.&lt;/p&gt;

&lt;p&gt;That narrowness makes the system easier to understand, debug, and trust.&lt;/p&gt;

&lt;p&gt;And if you’re building tools for cross-border e-commerce, &lt;a href="https://serpapi.com/amazon-search-api" rel="noopener noreferrer"&gt;SerpApi’s Amazon API&lt;/a&gt; provide a surprisingly rich source of product data. They made this entire workflow possible.&lt;/p&gt;

&lt;p&gt;If you’re working on product research, seller analytics, or marketplace intelligence, I’d recommend giving them a try.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Using SerpApi and DeepSeek to Break Down Dan Koe’s Content Strategy</title>
      <dc:creator>Magenta Qin</dc:creator>
      <pubDate>Mon, 01 Jun 2026 10:30:49 +0000</pubDate>
      <link>https://dev.to/magentaqin/using-serpapi-and-deepseek-to-break-down-dan-koes-content-strategy-2ka1</link>
      <guid>https://dev.to/magentaqin/using-serpapi-and-deepseek-to-break-down-dan-koes-content-strategy-2ka1</guid>
      <description>&lt;p&gt;Dan Koe is one of those creators who always seems to say the right thing at the right time. His videos consistently resonate around productivity, purpose, self-education, and building a meaningful life.&lt;/p&gt;

&lt;p&gt;At some point, I started wondering: Is there a repeatable pattern behind his content? Or is it mostly intuition?&lt;/p&gt;

&lt;p&gt;More importantly, can a creator's content strategy actually be broken down into something systematic and reproducible?&lt;/p&gt;

&lt;p&gt;So I ran a small experiment.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/serpapi/creator-lens" rel="noopener noreferrer"&gt;https://github.com/serpapi/creator-lens&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using SerpApi, I collected metadata, video details, and transcripts from Dan Koe’s YouTube channel(top 100 videos). Then I used DeepSeek to analyze the patterns behind the content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36bkgq1irrtscudnbnyy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36bkgq1irrtscudnbnyy.png" alt=" " width="800" height="458"&gt;&lt;/a&gt;&lt;br&gt;
Instead of simply watching the videos, I tried to reverse-engineer his strategy across four dimensions: content themes, title patterns, publishing cadence, and recurring core beliefs.&lt;/p&gt;

&lt;p&gt;What I found was far more structured, and far more revealing than I expected.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Engineering Approach
&lt;/h2&gt;

&lt;p&gt;To test this idea, I built a small internal tool called CreatorLens. The workflow was intentionally simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use SerpApi to collect YouTube data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Normalize the data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Send the structured payload to DeepSeek&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate strategy insights through AI analysis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Visualize the result in a dashboard&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.amazonaws.com%2Fuploads%2Farticles%2Fwwn58z97uoqncg4mxqkz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwn58z97uoqncg4mxqkz.png" alt="CreatorLens Workflow" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I didn’t use a database for this experiment. Instead, I wanted to see how far I could get with a lightweight, stateless pipeline built around APIs and structured prompts.&lt;/p&gt;

&lt;p&gt;The stack looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Input
↓
SerpApi YouTube Search API
↓
SerpApi YouTube Video API
↓
SerpApi YouTube Transcript API
↓
DeepSeek Analysis
↓
CreatorLens Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At a high level, SerpApi handled the data layer — searching videos, fetching metadata, and retrieving transcripts. And DeepSeek handled the reasoning layer — identifying recurring themes, extracting title patterns, clustering audience pain points, summarizing core beliefs, and generating strategy frameworks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8dfp6vgvg8sfl6lgmrut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8dfp6vgvg8sfl6lgmrut.png" alt="CreatorLens architecture: SerpApi handles data collection and normalization, while DeepSeek performs reasoning and strategy analysis." width="800" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;The interesting part wasn’t just collecting the data. It was turning unstructured YouTube content into something analyzable.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Collecting YouTube Data with SerpApi
&lt;/h2&gt;

&lt;p&gt;The first challenge for me was turning a YouTube channel into structured, analyzable data.&lt;/p&gt;

&lt;p&gt;Instead of manually opening videos one by one, I used SerpApi’s YouTube APIs to build a lightweight data pipeline. Just like what we mentioned before, the workflow looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;YouTube Search API
→ fetch videos

YouTube Video API
→ enrich metadata

YouTube Transcript API
→ retrieve spoken content

DeepSeek
→ analyze creator strategy

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

&lt;/div&gt;



&lt;p&gt;As I wanted to keep the implementation itself simple and stateless, I built a single API route in Next.js that orchestrated the entire flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search videos
→ fetch metadata
→ fetch transcripts
→ normalize data
→ send compact payload to DeepSeek
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep the implementation clean, I wrapped the SerpApi JavaScript SDK into a small reusable helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getJson&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;serpapi&lt;/span&gt;&lt;span class="dl"&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;serpapi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getJson&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&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;From there, I split the data collection logic into three focused functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;searchYouTubeVideos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;fetchVideoDetails&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;fetchTranscript&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each function handled a different layer of the YouTube dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching Videos
&lt;/h2&gt;

&lt;p&gt;The first function, &lt;code&gt;searchYouTubeVideos()&lt;/code&gt;, uses SerpApi’s YouTube Search API to retrieve videos from a creator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmjyhel9ahyfiffxkglz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmjyhel9ahyfiffxkglz.png" alt="How searchYoutubeVideos works" width="800" height="533"&gt;&lt;/a&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;serpapi&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;creatorName&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;From the response, I extracted the fields needed for analysis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;video ID&lt;/li&gt;
&lt;li&gt;title&lt;/li&gt;
&lt;li&gt;thumbnail&lt;/li&gt;
&lt;li&gt;view count&lt;/li&gt;
&lt;li&gt;publish date&lt;/li&gt;
&lt;li&gt;duration
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="nx"&gt;maxVideos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&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="na"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;&lt;/span&gt;&lt;span class="dl"&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="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="k"&gt;as&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;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thumbnail&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&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;static&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;views&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;publishedDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published_date&lt;/span&gt; &lt;span class="k"&gt;as&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;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="k"&gt;as&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;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added a small parseViews() helper to normalize values like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"1.2M views"
"842K views"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into actual numeric values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching Video Details
&lt;/h2&gt;

&lt;p&gt;But the fields for each video is not enough for me to analyze, so I added the second function, fetchVideoDetails(). Through SerpApi’s YouTube Video API, it gives me additional metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;serpapi&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;youtube_video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives more detailed information like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;description&lt;/li&gt;
&lt;li&gt;likes&lt;/li&gt;
&lt;li&gt;more accurate view counts
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="k"&gt;as&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;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;views&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseViews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseViews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;likes&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;I also intentionally wrapped the function in try/catch so the pipeline can fail gracefully if a single request breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching Transcripts
&lt;/h2&gt;

&lt;p&gt;The most important part of the pipeline was transcript retrieval.&lt;/p&gt;

&lt;p&gt;Using SerpApi’s YouTube Transcript API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;serpapi&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;youtube_video_transcript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;videoId&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;I extracted transcript snippets and joined them into plain text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;snippets&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the project shifted from simple analytics into actual strategy analysis.&lt;/p&gt;

&lt;p&gt;Titles and metrics tell you what performed.&lt;/p&gt;

&lt;p&gt;Transcripts reveal:&lt;/p&gt;

&lt;p&gt;**- recurring language and persuasion patterns&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mental models and emotional framing&lt;/li&gt;
&lt;li&gt;audience positioning**&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Composing Three Fetch Functions Into One Orchestration Layer
&lt;/h2&gt;

&lt;p&gt;Finally, I combined all three functions into a single orchestration layer.&lt;/p&gt;

&lt;p&gt;The goal of this function is simple: given a creator name, return a normalized list of videos with everything DeepSeek needs for analysis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchVideosWithDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;creatorName&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="nx"&gt;maxVideos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;VideoData&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, the pipeline does three things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Creator name
→ search relevant YouTube videos
→ fetch video metadata
→ fetch video transcripts
→ return normalized VideoData[]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each video, metadata and transcript are fetched in parallel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;fetchVideoDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;fetchTranscript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids unnecessary sequential API calls. The transcript does not depend on the metadata response, so both requests can run at the same time.&lt;/p&gt;

&lt;p&gt;The final output is a structured &lt;code&gt;VideoData[]&lt;/code&gt; array, where each item contains the video title, URL, metadata, and transcript. This gives DeepSeek a clean input format instead of forcing it to reason over scattered API responses.&lt;/p&gt;

&lt;p&gt;At a high level, SerpApi handled the full data layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Creator Name
→ YouTube Search Results
→ Video Metadata
→ Transcripts
→ Structured Dataset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this dataset was ready, I could move from data collection to the analysis layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using DeepSeek to Analyze Creator Strategy
&lt;/h2&gt;

&lt;p&gt;Once the YouTube data was normalized into a structured dataset, the next challenge was turning raw video content into creator-level strategy insights.&lt;/p&gt;

&lt;p&gt;My first attempt was the obvious one: truncate each transcript and send it directly to DeepSeek.&lt;/p&gt;

&lt;p&gt;At first, this actually worked surprisingly well. But after testing longer videos, I started noticing an important limitation: important arguments, storytelling structure, and recurring ideas often appear later in the video. By aggressively slicing the transcript, the analysis became biased toward only the beginning of the content.&lt;/p&gt;

&lt;p&gt;For example: a key insight might appear halfway through; the conclusion of an argument could be missing entirely; emotional framing develops gradually across the whole transcript.&lt;/p&gt;

&lt;p&gt;So I tried the opposite approach — sending multiple full transcripts into a single DeepSeek prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;views&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;
  &lt;span class="na"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That ran into different problems. &lt;strong&gt;Multiple long transcripts significantly increased latency and context usage, while introducing noisy spoken content — repeated phrases, sponsor segments, filler words. As the number of videos grew, reasoning quality degraded because too much irrelevant context competed for attention.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Either way, I was hitting the same wall: a context compression problem. Truncating loses signal; combining adds noise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fivwlq7soatdpwaziyi3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fivwlq7soatdpwaziyi3x.png" alt="The context compression problem. Naively truncating transcripts or combining multiple long transcripts into a single prompt either loses critical information or overwhelms the model, resulting in degraded reasoning quality and unreliable analysis." width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching to a Two-Step Analysis Pipeline
&lt;/h2&gt;

&lt;p&gt;So I redesigned the workflow into a hierarchical pipeline: instead of compressing information before understanding it, DeepSeek would understand each video independently first, then perform pattern analysis on those summaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1
Full transcript
→ DeepSeek
→ compact VideoSummary

Step 2
VideoSummary[]
→ DeepSeek
→ creator strategy analysis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This architecture improved analysis quality significantly because semantic compression happened after DeepSeek fully understood each individual video.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitss1u2susgbgxobt8zf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitss1u2susgbgxobt8zf.png" alt="The two-step analysis pipeline. Instead of truncating transcripts or analyzing hundreds of videos in a single prompt, CreatorLens first converts each full transcript into a structured VideoBrief, then performs cross-video analysis on the compressed summaries to identify recurring themes, title patterns, audience pain points, and creator strategy frameworks." width="798" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first stage extracted structured insights from each transcript independently.&lt;/p&gt;

&lt;p&gt;The second stage analyzed recurring strategic patterns across the entire channel using semantically dense summaries rather than raw text.&lt;/p&gt;

&lt;p&gt;The pipeline also became easier to scale because per-video summarization could run in parallel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing Structured AI Contracts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One important realization during development was that prompt engineering was less about “asking better questions” and more about designing reliable AI interfaces.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of treating DeepSeek like a chatbot, I treated it more like a structured analysis engine with strict input and output contracts.&lt;/p&gt;

&lt;p&gt;The prompts effectively became schema-driven interfaces between different stages of the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 Prompt: Per-Video Semantic Compression
&lt;/h2&gt;

&lt;p&gt;The first prompt focused on compressing a full YouTube video into a structured semantic representation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildVideoSummaryPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VideoData&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;views&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;publishedDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
You are a content analyst.

Analyze the YouTube video below and return a compact JSON summary.

Video data:

&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

Return ONLY valid JSON with exactly these fields:
{
  "videoId": "",
  "title": "",
  "views": 0,
  "publishedDate": "",
  "mainTopic": "",
  "keyPoints": [],
  "titlePattern": "",
  "hook": "",
  "whyItWorks": "",
  "audienceProblems": [],
  "beliefs": []
}
`&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;Instead of generating conversational summaries, the model was forced to extract structured strategic signals such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hooks&lt;/li&gt;
&lt;li&gt;title patterns&lt;/li&gt;
&lt;li&gt;audience pain points&lt;/li&gt;
&lt;li&gt;creator beliefs&lt;/li&gt;
&lt;li&gt;positioning strategies&lt;/li&gt;
&lt;li&gt;narrative structures
This transformed long transcripts into compact VideoSummary objects optimized for downstream reasoning.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2 Prompt: Cross-Video Strategy Analysis
&lt;/h2&gt;

&lt;p&gt;Once all videos were summarized, the second prompt analyzed creator-level patterns across the channel.&lt;/p&gt;

&lt;p&gt;At this stage, DeepSeek no longer needed raw transcripts. Instead, it reasoned over structured semantic abstractions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildAnalysisPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;summaries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VideoSummary&lt;/span&gt;&lt;span class="p"&gt;[]&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
You are a content strategy analyst.

Based on the per-video summaries below,
identify the creator's overall content strategy.

Video summaries:

&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summaries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;

Return ONLY valid JSON with exactly these fields:
{
  "mainNiche": "",
  "topContentThemes": [],
  "titlePatterns": [],
  "coreBeliefs": [],
  "audiencePainPoints": [],
  "publishingCadence": "",
  "videoAnalysis": [],
  "strategyReport": ""
}
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This created a hierarchy of reasoning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;raw transcript
→ semantic summary
→ creator-level strategy analysis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second-stage analysis became surprisingly effective at identifying patterns I wouldn't have caught by watching:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recurring hooks — emotional triggers reused across videos&lt;/li&gt;
&lt;li&gt;Identity-based positioning — how he frames the audience to themselves&lt;/li&gt;
&lt;li&gt;Reusable title formulas — structural templates behind high-performers&lt;/li&gt;
&lt;li&gt;Recurring beliefs — philosophical anchors that show up again and again&lt;/li&gt;
&lt;li&gt;Audience anxieties — the underlying fears the content addresses&lt;/li&gt;
&lt;li&gt;Publishing rhythm — cadence patterns across multi-year timeframes
At that point, the project stopped feeling like "YouTube analytics." It started feeling more like reverse-engineering a creator's communication system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Parallelizing the Pipeline
&lt;/h2&gt;

&lt;p&gt;Since each video could be summarized independently, the first stage was parallelized using Promise.all():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summaries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;summarizeVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This significantly improved throughput while avoiding unnecessary sequential API calls.&lt;/p&gt;

&lt;p&gt;Even when analyzing many videos, the pipeline remained reasonably fast because transcript summarization happened concurrently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling the DeepSeek API
&lt;/h2&gt;

&lt;p&gt;For the implementation, I used DeepSeek’s chat completion API with JSON response formatting enabled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEEPSEEK_API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deepseek-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_MESSAGE&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;response_format&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;json_object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added a strict system instruction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_MESSAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a JSON-only assistant. Always respond with valid JSON only.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enabling:&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="err"&gt;response_format:&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="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json_object"&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;significantly reduced malformed outputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing and Normalizing the AI Response
&lt;/h2&gt;

&lt;p&gt;Even with structured prompting, LLM responses can still fail occasionally.&lt;/p&gt;

&lt;p&gt;So instead of assuming the response would always be valid JSON, I added defensive parsing logic.&lt;/p&gt;

&lt;p&gt;DeepSeek sometimes returned markdown wrappers such as:&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I stripped those wrappers before parsing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;stripMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sr"&gt;/``&lt;/span&gt;&lt;span class="err"&gt;`
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;(?:&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="s2"&gt;```/
  );

  return match
    ? match[1].trim()
    : content.trim();
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I safely parsed the response:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;try {&lt;br&gt;
  return JSON.parse(cleaned);&lt;br&gt;
} catch {&lt;br&gt;
  return {};&lt;br&gt;
}&lt;/code&gt;&lt;br&gt;
Finally, I normalized all fields with fallback values:&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="nx"&gt;mainNiche&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mainNiche&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;topContentThemes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topContentThemes&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="nx"&gt;titlePatterns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;titlePatterns&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This made the overall pipeline much more resilient.&lt;br&gt;
Even if the model returned partial or malformed output, the dashboard could still render safely instead of crashing entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Pipeline Found
&lt;/h2&gt;

&lt;p&gt;Running the full pipeline on Dan Koe's top 100 videos surfaced a strategy that was more systematic than I expected. Across 35.2M total views and 352K average views per video, four patterns kept reappearing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9ufwt406f2ed22kyyt9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9ufwt406f2ed22kyyt9.png" alt="CreatorLens dashboard summarizing the analysis of Dan Koe’s top 100 videos. The system identifies recurring content themes, dominant title patterns, publishing cadence, and aggregate performance metrics to reveal the underlying structure of the channel’s content strategy." width="800" height="462"&gt;&lt;/a&gt;&lt;br&gt;
His real topic isn't AI, or productivity, or skills. It's transformation. The pipeline grouped his 100 videos into five clusters: rapid life transformation, productivity and focus, AI and the future of work, skill development, and mindset and discipline. On paper, five different subjects. In practice, five different costumes for the same promise: you can become someone else, and fast. A title like "How To Use AI Better Than 99% Of People" looks like an AI video. But strip the AI away and what's actually being sold is become elite. The subject rotates; the topic doesn't.&lt;/p&gt;

&lt;p&gt;His titles run on two engines. Either he's promising you something impossibly fast — "How to fix your entire life in 1 day," "How To Become So Self-Disciplined It Feels Illegal" — or he's telling you you're doing it wrong: "I'm begging you to start writing essays," "You are learning the wrong skills." Desire on one side, fear on the other. And once you notice it, you start seeing it in every title. The mechanic underneath both is the same — the title plants a question your brain can't answer without watching. Fix my life in a day? How? Wrong skills? Which ones? That question is the hook. The click is just the brain trying to close the loop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhderifbds3g8cutrdqtl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhderifbds3g8cutrdqtl.png" alt=" " width="800" height="642"&gt;&lt;/a&gt;&lt;br&gt;
Publishing cadence is consistent but not frequent. Roughly one video per month, with occasional gaps of up to three months. He's not playing the volume game. Each video is a deliberate piece of content carrying high production value, which fits the audience he's building — viewers who want depth, not constant noise.&lt;/p&gt;

&lt;p&gt;The core beliefs are remarkably stable across years. Four ideas appear again and again, regardless of topic: radical change is possible through focused action; personal agency and discipline are the keys to success; AI and modern tools can dramatically accelerate growth; continuous learning and adaptability are essential. These aren't claims Dan Koe argues for — they're claims he argues from. Every video assumes them and builds on them. That's what makes the content feel coherent across hundreds of videos: it's not a feed of topics, it's a worldview being expressed across many surfaces.&lt;/p&gt;

&lt;p&gt;The most interesting finding wasn't any single one of these — it was the combination.&lt;/p&gt;

&lt;p&gt;The framework underneath the channel is something like: identify a specific audience pain point (feeling stuck, lacking discipline, fearing obsolescence) → offer a contrarian or high-impact solution → package it with a hyperbolic or pattern-interrupt title → deliver actionable advice that reinforces the four core beliefs.&lt;/p&gt;

&lt;p&gt;That's not intuition. That's a repeatable system. And it's exactly the kind of thing that's nearly impossible to see by watching the videos one at a time — but obvious once you compress 100 of them into structured summaries and analyze them together.&lt;br&gt;
Final Thoughts&lt;br&gt;
At first, I thought I was simply building a YouTube analytics tool.&lt;/p&gt;

&lt;p&gt;But once transcripts, metadata, and structured AI analysis started working together, the project became something much more interesting: a creator strategy reverse-engineering engine.&lt;/p&gt;

&lt;p&gt;One of the biggest insights from this experiment was that LLMs become significantly more powerful when they are treated as structured reasoning systems rather than conversational chatbots.&lt;/p&gt;

&lt;p&gt;The largest improvement did not come from changing models.&lt;/p&gt;

&lt;p&gt;It came from redesigning the architecture around reasoning workflows:&lt;/p&gt;

&lt;p&gt;full-context transcript understanding&lt;br&gt;
semantic compression&lt;br&gt;
hierarchical reasoning&lt;br&gt;
structured AI contracts&lt;br&gt;
resilient parsing&lt;br&gt;
parallel orchestration&lt;br&gt;
For this experiment, I also intentionally kept the backend completely stateless.&lt;/p&gt;

&lt;p&gt;There was:&lt;/p&gt;

&lt;p&gt;no database&lt;br&gt;
no queue system&lt;br&gt;
no background jobs&lt;br&gt;
Every request fetched fresh YouTube data directly from SerpApi, normalized it in memory, performed multi-stage DeepSeek analysis, and returned a dashboard-ready response.That kept the architecture surprisingly simple while still producing rich creator-level insights.&lt;/p&gt;

&lt;p&gt;More importantly, it reinforced something I’ve been thinking about recently:&lt;/p&gt;

&lt;p&gt;Modern AI applications increasingly behave less like traditional CRUD software and more like orchestration pipelines built around reasoning models. And honestly, that was probably the most exciting part of the entire project.&lt;/p&gt;

&lt;p&gt;If you want to build similar AI-powered analysis tools on top of real-time search and creator data, SerpApi provides a very clean developer experience for working with YouTube search, video metadata, and transcripts.&lt;/p&gt;

&lt;p&gt;The demo app in this article uses the following APIs from SerpApi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SerpApi YouTube Search API&lt;/li&gt;
&lt;li&gt;SerpApi YouTube Video API&lt;/li&gt;
&lt;li&gt;SerpApi YouTube Transcript API
Give them a try in your own AI-native applications and analytical workflows.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Use which: Interface or Type Alias in Typescript?</title>
      <dc:creator>Magenta Qin</dc:creator>
      <pubDate>Wed, 04 Jan 2023 05:01:21 +0000</pubDate>
      <link>https://dev.to/magentaqin/use-which-interface-or-type-alias-in-typescript-3cj4</link>
      <guid>https://dev.to/magentaqin/use-which-interface-or-type-alias-in-typescript-3cj4</guid>
      <description>&lt;p&gt;This blog mainly discusses the key differences of &lt;code&gt;interface vs type&lt;/code&gt;, and share how I would use them in my daily software development routine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Describe the shape of an object or a function signature
&lt;/h3&gt;

&lt;p&gt;Both can be used to describe the shape of an object or a function signature. &lt;/p&gt;

&lt;p&gt;However, I mostly use &lt;code&gt;interface&lt;/code&gt; to describe the object, and use &lt;code&gt;type&lt;/code&gt; to describe a function signature, as  I can clearly know it’s a definition for function from &lt;code&gt;arrow function&lt;/code&gt;, which is impossible to realize in &lt;code&gt;interface&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SetPoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another scenario where I use interface is for 「Index Signatures」. For example, in dynamic generated input,  we don’t know all the names of a type’s properties ahead of time, but we do know the shape of the values.&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&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;
  
  
  Extending and Intersection Types
&lt;/h3&gt;

&lt;p&gt;At first sight, extending and intersection are similar ways to combine types. However, the principle difference between the two is &lt;strong&gt;how conflicts are handled&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;interface&lt;/code&gt;, If you declare a property with the same key, it will overrides the one in the derived interface. However, it should be compatible with the previous one, or typescript compiler will throw an error.&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;// This works pretty well, as it is compatible&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Converter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StringNumberConverter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Converter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Oops...this will throw an error, as it is incompatible&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Converter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StringNumberConverter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Converter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F856p7ujgy7jrycm9o9rw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F856p7ujgy7jrycm9o9rw.png" alt=" " width="800" height="225"&gt;&lt;/a&gt;&lt;br&gt;
For type, properties with the same key don’t have to be compatible. You can declare whatever you like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Converter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StringNumberConverter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Converter&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="kr"&gt;string&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;Another difference you should pay attention is the meaning they represent. When I use interface, I want it stands for the「Parent-Child」relationship. By contrast, when I use type, it only stands for 「Composition」relationship.&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;// 「Parent-Child」relationship. Cat derives from Animal.&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Cat&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 「Composition」relationship. Color and Size are two seperate species&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Color&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;small&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;medium&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;large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ColorSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Color&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Class Implements
&lt;/h3&gt;

&lt;p&gt;A class can implement an interface or type alias, but it can not implement Union type, as in typescript, a class can only implement an object type or intersection of object types with &lt;strong&gt;statically known members&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;So in daily development, if this type is designed to be implemented by class, I will use &lt;code&gt;interface&lt;/code&gt; over &lt;code&gt;type&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PartialPoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// can not implement a union type&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomePartialPoint&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;PartialPoint&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&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;
  
  
  More than Object Types
&lt;/h3&gt;

&lt;p&gt;Unlike an interface, type alias can also be used for other types. Frequently used are such as  union type and tuples.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StringNumberPair&lt;/span&gt; &lt;span class="o"&gt;=&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="kr"&gt;number&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;doSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stringHash&lt;/span&gt;&lt;span class="p"&gt;:&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;inputString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stringHash&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;inputString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// string&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;hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// number        &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Declaration Merging
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;interface&lt;/code&gt; is open, and &lt;code&gt;type alias&lt;/code&gt; is close within itself. An interface can be defined multiple times, and will be treated as a single interface with members of all declarations being merged.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Bear&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Error：Duplicate Identifier&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Bear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// works pretty well&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Bear&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Bear&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Bear&lt;/span&gt; &lt;span class="o"&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="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;age&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Working With &lt;code&gt;typeof&lt;/code&gt;  Type Operator
&lt;/h3&gt;

&lt;p&gt;In most cases, if you want to work with &lt;code&gt;typeof&lt;/code&gt; type operator, choose &lt;code&gt;type&lt;/code&gt; over &lt;code&gt;interface&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;f&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="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
** type P = {
    x: number;
    y: number;
}
**/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;When the next time you are confused which to use, try this cheatsheet!&lt;/p&gt;

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

</description>
      <category>discuss</category>
      <category>development</category>
      <category>wordpress</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Implement Dark Mode in Vue/React UI Component Library</title>
      <dc:creator>Magenta Qin</dc:creator>
      <pubDate>Thu, 29 Dec 2022 13:00:16 +0000</pubDate>
      <link>https://dev.to/magentaqin/implement-dark-mode-in-vuereact-ui-component-library-3g1e</link>
      <guid>https://dev.to/magentaqin/implement-dark-mode-in-vuereact-ui-component-library-3g1e</guid>
      <description>&lt;p&gt;Unlike other blogs only telling you how to implement Dark Mode using React Hooks to toggle theme, this blog will explain how Dark Mode will be implemented across&amp;nbsp;&lt;strong&gt;the whole Design to Code stages from a bottom-top side&lt;/strong&gt;. The stages include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design token and style variable naming conventions&lt;/li&gt;
&lt;li&gt;Css variables and Javascript color variables&lt;/li&gt;
&lt;li&gt;Global dark mode implementation&lt;/li&gt;
&lt;li&gt;Customize component-level dark mode color&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Design token and style variable naming conventions
&lt;/h2&gt;

&lt;p&gt;Dark and Light mode share the same token name. Let's take the ProgressIndicator component as an example. The color of the circle changes with the Dark/Light mode, but they share the same Design Token--&lt;code&gt;Fill&lt;/code&gt;.&lt;/p&gt;

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

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

&lt;p&gt;Speaking of the css variable naming convention, we add prefix to differentiate them.&lt;/p&gt;

&lt;p&gt;For the color that will change with the mode, we name&amp;nbsp;&lt;code&gt;Theme&lt;/code&gt;prefix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="c1"&gt;// dark/index.styl&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;Fill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="nc"&gt;.2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;SecondaryFill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="nc"&gt;.1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;light&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;index&lt;/span&gt;&lt;span class="nc"&gt;.styl&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;Fill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="nc"&gt;.08&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;SecondaryFill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="nc"&gt;.04&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;index&lt;/span&gt;&lt;span class="nc"&gt;.styl&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;ThemeFill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--ThemeFill&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;Fill&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;ThemeSecondaryFill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--ThemeSecondaryFill&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;SecondaryFill&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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.amazonaws.com%2Fuploads%2Farticles%2Fwely3tp94hriohovkezx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwely3tp94hriohovkezx.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqtcyttrpg8emsj3sd87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqtcyttrpg8emsj3sd87.png" alt=" " width="800" height="200"&gt;&lt;/a&gt;&lt;br&gt;
For the colors that will not change with the dark or light mode, we name&amp;nbsp;Always as the prefix of the Token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="c1"&gt;// always/index.styl&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;AlwaysWhiteFill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="nc"&gt;.99&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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.amazonaws.com%2Fuploads%2Farticles%2F3j3sg6e3utqemqdf915r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j3sg6e3utqemqdf915r.png" alt=" " width="800" height="95"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS variables and Javascript color variables
&lt;/h2&gt;

&lt;p&gt;As we've already provided stylus variables, do we need css variables?&lt;/p&gt;

&lt;p&gt;The answer is YES.&lt;/p&gt;

&lt;p&gt;Stylus variables are a part of CSS preprocessors,  and need to be compiled into CSS to be understood by the browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nv"&gt;$Divider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;.main-header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nv"&gt;$Divider&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;For example, the code would be compiled into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.main-header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="mh"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the code compiles, the variables are gone.&lt;/p&gt;

&lt;p&gt;CSS variables, however, is natively supported within CSS. It &lt;strong&gt;doesn’t need to be compiled&lt;/strong&gt;, and can be directly used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;--Divider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.main-header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Divider&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;So what is the biggest advantage of CSS variables vs preprocessor variables? &lt;/p&gt;

&lt;p&gt;Through CSS variables, styles are changed &lt;strong&gt;in runtime instead of compilation time&lt;/strong&gt;. This will bring following benefits:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BENEFIT 1&lt;/strong&gt;:  Enable us to &lt;strong&gt;reset style in runtime,&lt;/strong&gt; which is impossible with preprocessor variables.&lt;/p&gt;

&lt;p&gt;For example, in grid layout, media query could change the variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&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="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;450px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;:root&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="m"&gt;60%&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;Another example is that if you use preprocessor variables to switch UI mode, you have to provide two different preprocessor variables to the same value, and you have to write many duplicated code to tell the browser to use the right color, which enlarges the code size and make it hard to manage styles.  e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="c1"&gt;// dark mode&lt;/span&gt;
&lt;span class="nv"&gt;$DarkDivider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#eee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// light mode&lt;/span&gt;
&lt;span class="nv"&gt;$LightDivider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// component example&lt;/span&gt;
&lt;span class="nc"&gt;.divider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nv"&gt;$LightDivider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;.dark&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nv"&gt;$DarkDivider&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;However, if you use css variables, you don’t have to provide two different variables for the same value. And you don’t have to write duplicated code. When you switch class name, In light mode, browser will use &lt;code&gt;#ddd&lt;/code&gt;, and in dark mode, browser will use &lt;code&gt;#eee&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="c1"&gt;// light style&lt;/span&gt;
&lt;span class="na"&gt;--Divider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Dark style&lt;/span&gt;
&lt;span class="na"&gt;--Divider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#eee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// index.css&lt;/span&gt;
&lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.light-mode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;--Divider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.dark-mode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;--Divider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#eee&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;BENEFIT 2: You could manipulate them in JavaScript.  In component library, you could only allow users to pass Color Token instead of pass any rgba or hex values, which brings chaos and hard to manage styles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="c1"&gt;// colors.js&lt;/span&gt;
&lt;span class="nt"&gt;const&lt;/span&gt; &lt;span class="nt"&gt;ThemeGray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--ThemeGray&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;

&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;demo&lt;/span&gt;&lt;span class="nc"&gt;.vue&lt;/span&gt;
&lt;span class="nt"&gt;textStyle&lt;/span&gt;&lt;span class="nc"&gt;.color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;colors&lt;/span&gt;&lt;span class="nc"&gt;.ThemeGray&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Despite differences between preprocessor variables and less variables, they can work together. Let’s have a look! &lt;/p&gt;

&lt;h2&gt;
  
  
  Global dark mode implementation
&lt;/h2&gt;

&lt;p&gt;In most time, we apply theme changes globally.  You could either implement in the component library like &lt;a href="https://vant-contrib.gitee.io/vant/#/en-US/config-provider" rel="noopener noreferrer"&gt;Vant&lt;/a&gt;, or recommend your users to use ready-made solution like &lt;a href="https://github.com/nuxt-modules/color-mode" rel="noopener noreferrer"&gt;&lt;code&gt;@nuxtjs/color-mode&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
Let’s see how they are working under the hood.&lt;/p&gt;
&lt;h3&gt;
  
  
  Vant Solution—ConfigProvider
&lt;/h3&gt;

&lt;p&gt;To enable ConfigProvider, you should register component globally.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createApp&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;vue&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;ConfigProvider&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;vant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ConfigProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can enable dark mode like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;van-config-provider theme="dark"&amp;gt;
  ...
&amp;lt;/van-config-provider&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ConfigProvider is essentially a component. It will watch the theme props, and dynamically add or remove class to &lt;code&gt;document.documentElement&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Vant provides css variables for dark theme. When you add class van-them-black, it will use these css variables to replace light theme css variables.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Nuxt Solution—ColorMode Module
&lt;/h3&gt;

&lt;p&gt;Nuxt module enables us to share custom solutions as npm packages without adding unnecessary boilerplate.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@nuxtjs/color-mode&lt;/code&gt; mainly provide two plugins (&lt;code&gt;plugin.client&lt;/code&gt;and &lt;code&gt;plugin.server&lt;/code&gt;）and one script. Let’s see how they are working together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndtwxj09dp2wnevg13cy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndtwxj09dp2wnevg13cy.png" alt=" " width="800" height="932"&gt;&lt;/a&gt;&lt;br&gt;
To be mentioned, on the server side, it will use preference options color as initial colorMode, and the colorMode class has not been added to the template, and the css variables are loaded asynchronously. So like below, even you set the app to be dark mode, you will see the preview is still in light mode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rmd9ebfdd940agymv76.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rmd9ebfdd940agymv76.png" alt=" " width="800" height="760"&gt;&lt;/a&gt;&lt;br&gt;
So when will the dark mode class be added? &lt;/p&gt;

&lt;p&gt;On the client side, the browser begins to execute script. It gets the exact colorMode from localStorage or system color or the forced set color. Then it will set className to document.documentElement, which will let the browser know to use the relevant mode css variables. &lt;/p&gt;

&lt;p&gt;Then, client plugin begins to watch color mode changes from user and watch colorMode changes from the router options.&lt;/p&gt;
&lt;h3&gt;
  
  
  Customize component-level dark mode color
&lt;/h3&gt;

&lt;p&gt;Instead of using the dark mode color provided by the component library, sometimes we want to customize the dark mode color for certain component. &lt;/p&gt;

&lt;p&gt;The easiest way to do this is to wrap dark mode class outside the component class. You can directly use &lt;code&gt;dark-mode&lt;/code&gt; class, but it would be a little bit disordered, as dark-mode class is different from other classes.&lt;/p&gt;

&lt;p&gt;Why don’t we provide a stylus function &lt;code&gt;prefers-color-scheme&lt;/code&gt;to users?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="na"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="err"&gt;)
  &lt;/span&gt;&lt;span class="na"&gt;themes&lt;/span&gt;&lt;span class="err"&gt; = &lt;/span&gt;&lt;span class="na"&gt;light&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;dark&lt;/span&gt;&lt;span class="err"&gt;
  &lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="err"&gt;('&lt;/span&gt;&lt;span class="na"&gt;invalid&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;') unless scheme in themes&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s1"&gt;  if scheme == light&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;    &amp;amp;, .light-mode &amp;amp;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;      {block}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;  else if scheme == dark&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;    .dark-mode &amp;amp;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;      {block}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we want to customize the dark mode color for certain component, we simply use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.reds-button-new.outlined&lt;/span&gt;
  &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nt"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;dark&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nt"&gt;background-color&lt;/span&gt; &lt;span class="nt"&gt;red&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will greatly improve the code readability.&lt;/p&gt;

&lt;p&gt;That’s all. Feel free to ask me any questions about dark mode.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
