<?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: Bright Emmanuel</title>
    <description>The latest articles on DEV Community by Bright Emmanuel (@brighto7700).</description>
    <link>https://dev.to/brighto7700</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3782103%2F9b51831b-4853-4f4c-831a-d69639e935fe.png</url>
      <title>DEV Community: Bright Emmanuel</title>
      <link>https://dev.to/brighto7700</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brighto7700"/>
    <language>en</language>
    <item>
      <title>The Solo Dev Cheat Code: Building Fast with Next.js, Supabase, and Vercel in 2026</title>
      <dc:creator>Bright Emmanuel</dc:creator>
      <pubDate>Sat, 28 Mar 2026 19:41:36 +0000</pubDate>
      <link>https://dev.to/brighto7700/the-solo-dev-cheat-code-building-fast-with-nextjs-supabase-and-vercel-in-2026-7e4</link>
      <guid>https://dev.to/brighto7700/the-solo-dev-cheat-code-building-fast-with-nextjs-supabase-and-vercel-in-2026-7e4</guid>
      <description>&lt;p&gt;I didn't build ShellSignal in ideal conditions.&lt;/p&gt;

&lt;p&gt;I built it in a hostel in Nigeria, during JUPEB exams, on a limited data plan,&lt;br&gt;
for a DEV Weekend Challenge with a hard deadline. No team. No staging server.&lt;br&gt;
No time to configure Webpack or wire up a separate Express backend.&lt;/p&gt;

&lt;p&gt;The stack either held up under pressure or it didn't.&lt;/p&gt;

&lt;p&gt;It did. And this is the breakdown of why.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Solo Dev Tax
&lt;/h2&gt;

&lt;p&gt;Before you write a single line of product code, you're already bleeding time:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Do I use Vite or CRA? Where does my backend live? How do I handle auth tokens? Which cloud provider? Do I set up CI/CD now or later?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two hours later, you have a configured project and zero features.&lt;/p&gt;

&lt;p&gt;You are the frontend engineer, the DBA, the DevOps team, and the product&lt;br&gt;
manager—simultaneously. Infrastructure setup and boilerplate auth can consume&lt;br&gt;
&lt;strong&gt;70% of your time&lt;/strong&gt; before you've touched the thing that actually matters:&lt;br&gt;
your core product.&lt;/p&gt;

&lt;p&gt;In 2026, my answer to that is the &lt;strong&gt;Next.js + Supabase + Vercel&lt;/strong&gt; pipeline.&lt;br&gt;
Here's exactly how it works and why.&lt;/p&gt;


&lt;h2&gt;
  
  
  Next.js: One Repo, Full Stack
&lt;/h2&gt;

&lt;p&gt;Two &lt;code&gt;package.json&lt;/code&gt; files. Two dev servers. Two deployment pipelines. Your&lt;br&gt;
React frontend lives in one repo, your Node/Express backend in another, and&lt;br&gt;
your brain is the unpaid glue holding them together.&lt;/p&gt;

&lt;p&gt;Next.js collapses that into a single repository.&lt;/p&gt;

&lt;p&gt;Your database query and the button that triggers it live in the same file.&lt;br&gt;
This is not a minor convenience—it's a completely different way of working.&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;// app/actions/saveArticle.ts&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use server&lt;/span&gt;&lt;span class="dl"&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;createClient&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;@/lib/supabase/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&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;saveArticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;articleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&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;createClient&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&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;error&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="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;saved_articles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;article_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;articleId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/components/SaveButton.tsx&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;saveArticle&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;@/actions/saveArticle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SaveButton&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;articleId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;articleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;saveArticle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="nx"&gt;articleId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server Action and UI component. Same codebase. No API contract to maintain&lt;br&gt;
between repos. No extra round trip. No context switch.&lt;/p&gt;

&lt;p&gt;The App Router gives you file-based routing, layouts, and streaming out of the&lt;br&gt;
box. You scaffold with &lt;code&gt;create-next-app&lt;/code&gt; and the structure is already sensible.&lt;br&gt;
No Webpack config. No custom Babel setup.&lt;/p&gt;


&lt;h2&gt;
  
  
  Supabase: The Database That Comes With Everything
&lt;/h2&gt;

&lt;p&gt;The managed backend space is crowded. Most options make you choose: speed of&lt;br&gt;
setup, or power of queries. Pick one.&lt;/p&gt;

&lt;p&gt;Supabase doesn't make you choose. You get &lt;strong&gt;PostgreSQL&lt;/strong&gt;—a real relational&lt;br&gt;
database with joins, transactions, and SQL you actually control—wrapped in an&lt;br&gt;
API fast enough to use on day one of a weekend project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auth is a single client call:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/supabase/client.ts&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;createBrowserClient&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;@supabase/ssr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBrowserClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Sign in with GitHub — no token logic, no session handling&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signInWithOAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github&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;No token management. No refresh logic. No session handling from scratch.&lt;br&gt;
Configure OAuth providers from the dashboard and move on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row Level Security kills an entire class of bugs:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Users can only read their own saved articles&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"Users read own saves"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;saved_articles&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Users can only insert their own saves&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"Users insert own saves"&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;saved_articles&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No middleware. No &lt;code&gt;if (article.userId !== req.user.id) return 403&lt;/code&gt;&lt;br&gt;
scattered across your route handlers. Authorization lives at the database&lt;br&gt;
level, enforced before your code even runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-time in three lines:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;articles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres_changes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;articles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setArticles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&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="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No WebSocket server. No socket.io config. The data flows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Vercel: The DevOps You Never Have to Think About
&lt;/h2&gt;

&lt;p&gt;When you're solo, DevOps is the tax you pay for every feature you ship.&lt;br&gt;
Vercel makes that tax as small as it can possibly be.&lt;/p&gt;

&lt;p&gt;Push to &lt;code&gt;main&lt;/code&gt;. Your app is live globally in seconds. No deployment scripts.&lt;br&gt;
No SSH. No NGINX config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preview deployments&lt;/strong&gt; mean every PR gets a unique live URL. Share it with a&lt;br&gt;
client, test on a different device, or just sanity-check before merging.&lt;br&gt;
It's a full QA environment you never had to provision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cron jobs without a server:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;vercel.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"crons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/curate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 6 * * *"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/curate/route.ts&lt;/span&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;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Runs every morning at 6am — fetch, summarize, store&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&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;fetchTrendingArticles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;summarizeAndStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That cron wakes up every morning and curates ShellSignal's feed before a&lt;br&gt;
single user opens the app. No external scheduler. No cron server. Just a&lt;br&gt;
JSON config and a route handler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mobile angle:&lt;/strong&gt; Because Vercel handles the build layer entirely, your&lt;br&gt;
deployment pipeline doesn't require a machine. Commit from GitHub's web&lt;br&gt;
editor. Check build logs from a browser tab. For those of us building&lt;br&gt;
entirely from Android, this is the difference between shipping and not shipping.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It All Came Together: ShellSignal
&lt;/h2&gt;

&lt;p&gt;The problem I was solving was personal. As a student with constrained&lt;br&gt;
bandwidth, keeping up with tech news was expensive. Most aggregators surface&lt;br&gt;
everything. I needed signal, not noise.&lt;/p&gt;

&lt;p&gt;Here's how the stack mapped to the actual product:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next.js&lt;/strong&gt; handled the full application layer. UI, API routes, and the&lt;br&gt;
OpenRouter integration—Grok and Llama, model-routed depending on the request.&lt;br&gt;
Every AI summary call ran through a Next.js API route. The GitHub Trending&lt;br&gt;
cross-reference that powers the Dev Health badge—Stars vs. Open Issues scored&lt;br&gt;
into a single metric—that logic lives in the same codebase as the component&lt;br&gt;
that renders it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supabase&lt;/strong&gt; managed user accounts and saved articles. The part worth calling&lt;br&gt;
out is what I &lt;em&gt;didn't&lt;/em&gt; write. No token refresh logic. No authorization guards&lt;br&gt;
in route handlers. RLS handled it at the database level. For a weekend build&lt;br&gt;
under exam pressure, that's hours returned to the actual product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vercel&lt;/strong&gt; ran the morning cron that curates the feed and the edge functions&lt;br&gt;
that keep AI summary latency low regardless of where the user hits the app&lt;br&gt;
from. Aggressive caching kept bandwidth costs minimal—which mattered because&lt;br&gt;
I understood that user firsthand.&lt;/p&gt;

&lt;p&gt;The whole thing deployed from a push. I checked the build logs from my phone.&lt;/p&gt;

&lt;p&gt;One developer. One weekend. One repo. A live, AI-integrated, real-time news&lt;br&gt;
dashboard at the end of it.&lt;/p&gt;




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

&lt;p&gt;You will eventually outgrow parts of this stack. That's fine—it means you&lt;br&gt;
shipped something worth outgrowing.&lt;/p&gt;

&lt;p&gt;Until then, the goal is to spend your hours on the problem you're solving,&lt;br&gt;
not the infrastructure holding it together. This stack does that better than&lt;br&gt;
anything else I've used.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Over to you:&lt;/strong&gt; What's your go-to stack for shipping solo projects in 2026?&lt;br&gt;
Are you team relational DB or NoSQL? Drop it in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>productivity</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Built a Full-Stack Developer Platform From My Android Phone (No Laptop, No Excuses)</title>
      <dc:creator>Bright Emmanuel</dc:creator>
      <pubDate>Sun, 15 Mar 2026 00:22:22 +0000</pubDate>
      <link>https://dev.to/brighto7700/i-built-a-full-stack-developer-platform-from-my-android-phone-no-laptop-no-excuses-37ol</link>
      <guid>https://dev.to/brighto7700/i-built-a-full-stack-developer-platform-from-my-android-phone-no-laptop-no-excuses-37ol</guid>
      <description>&lt;p&gt;My entire development setup is my phone.&lt;/p&gt;

&lt;p&gt;No laptop. No desktop. No "I'll do it when I get home." Just a smartphone, Termux, and a GitHub web editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/brighto7700/no-laptop-no-problem-setting-up-a-professional-kotlin-java-environment-in-termux-4hbg"&gt;I've written about this workflow before&lt;/a&gt; — from building ShellSignal — a retro terminal-style developer dashboard with real-time tech news and AI summaries — to competing in a DEV Weekend Challenge with it. But this time I didn't just write about it — I built a platform for it. Meet &lt;strong&gt;Forge&lt;/strong&gt; — a developer hub built specifically for mobile coders, featuring a searchable script vault, interactive Termux guides, GitHub OAuth, and a one-tap environment configurator that generates a real &lt;code&gt;curl | bash&lt;/code&gt; command on the fly.&lt;/p&gt;

&lt;p&gt;And yes, I built the entire thing from my phone. Here's how.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Every time someone asks "how do I code on Android?" they get one of two answers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A five-year-old Reddit thread with broken links&lt;/li&gt;
&lt;li&gt;A YouTube video of someone half-explaining Termux for 20 minutes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's no dedicated resource. No curated shell scripts. No "run this one command and you're set up." Just scattered info that assumes you have a laptop nearby.&lt;/p&gt;

&lt;p&gt;I live this problem. I build real software from my phone daily. So I built the solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Forge?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;forge.brgt.site&lt;/strong&gt; is a developer hub for mobile coders with three core features:&lt;/p&gt;

&lt;h3&gt;
  
  
  🗂️ The Script Vault
&lt;/h3&gt;

&lt;p&gt;A library of shell scripts built specifically for Termux. Each script has a one-line &lt;code&gt;curl | bash&lt;/code&gt; install command, difficulty rating, chipset compatibility, and links to the raw file on GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  📖 Interactive Guides
&lt;/h3&gt;

&lt;p&gt;Step-by-step walkthroughs for setting up real dev environments on Android. Written by someone who actually uses them — no laptop assumed, no steps skipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ The One-Tap Configurator
&lt;/h3&gt;

&lt;p&gt;This is the magic feature. Pick your runtime (Node.js, Python, Go, Java), shell, and tools. Hit generate. Get a permanent &lt;code&gt;curl&lt;/code&gt; URL you can run straight in Termux.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://forge.brgt.site/api/config/bm9kZS1sdH | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That URL runs a fully customized setup script on your device. Permanently saved, shareable, and works on any Android phone.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;Everything runs on what I know best:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 14&lt;/strong&gt; (App Router) — SSG for SEO, server components for auth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; — Postgres DB, GitHub OAuth, Row Level Security&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; — mobile-first, dark terminal aesthetic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; — zero-config deploys, edge functions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MDX&lt;/strong&gt; — guides as files, editable directly from GitHub's web editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No local dev environment. Every file was created through GitHub's web editor on Android Chrome and deployed automatically via Vercel on each commit.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Build Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Starting with SEO in mind
&lt;/h3&gt;

&lt;p&gt;Most dev tools are an afterthought on search. Forge was designed SEO-first from line one.&lt;/p&gt;

&lt;p&gt;Every script in the Vault gets its own statically generated page at &lt;code&gt;/vault/[slug]&lt;/code&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unique title, description, and canonical URL&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SoftwareSourceCode&lt;/code&gt; JSON-LD schema&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BreadcrumbList&lt;/code&gt; JSON-LD schema&lt;/li&gt;
&lt;li&gt;Long-tail keyword targeting ("Termux Node.js setup", "shell scripts Android")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every guide gets a &lt;code&gt;HowTo&lt;/code&gt; JSON-LD schema — Google's favorite format for tutorials. The sitemap auto-generates from both Supabase content and MDX files.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Configurator — client-side script generation
&lt;/h3&gt;

&lt;p&gt;The one-tap configurator was the feature I was most excited about. Here's how it works under the hood:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User picks options (runtime, version, shell, tools)&lt;/li&gt;
&lt;li&gt;A pure TypeScript function generates a custom &lt;code&gt;setup.sh&lt;/code&gt; script client-side&lt;/li&gt;
&lt;li&gt;The script gets saved to Supabase with a unique config ID&lt;/li&gt;
&lt;li&gt;A Next.js API route serves the script at &lt;code&gt;/api/config/[id]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;User gets a permanent &lt;code&gt;curl | bash&lt;/code&gt; URL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The script generation is entirely deterministic — no AI, no server-side rendering, just a function that builds a bash script from options. Here's a simplified version:&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;generateScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConfigOptions&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;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;`#!/data/data/com.termux/files/usr/bin/bash`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`DEBIAN_FRONTEND=noninteractive pkg update -y &amp;amp;&amp;amp; pkg upgrade -y --force-confold`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`pkg install -y nodejs-lts`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`pkg install -y git`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;lines&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="se"&gt;\n&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;h3&gt;
  
  
  Auth from a phone
&lt;/h3&gt;

&lt;p&gt;GitHub OAuth with Supabase SSR on Next.js App Router was the hardest part of this build. The PKCE flow requires the code verifier to be stored in cookies, and getting that to work correctly across Vercel's edge runtime took more debugging than I'd like to admit.&lt;/p&gt;

&lt;p&gt;The fix was upgrading &lt;code&gt;@supabase/ssr&lt;/code&gt; to &lt;code&gt;^0.5.0&lt;/code&gt; which fixed the PKCE cookie handling, and making sure middleware never intercepts the &lt;code&gt;/auth/callback&lt;/code&gt; route.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mobile workflow
&lt;/h3&gt;

&lt;p&gt;Every file in this project was created using GitHub's web editor on Android Chrome. The workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open file in GitHub&lt;/li&gt;
&lt;li&gt;Edit or create&lt;/li&gt;
&lt;li&gt;Commit directly to main&lt;/li&gt;
&lt;li&gt;Vercel auto-deploys in ~60 seconds&lt;/li&gt;
&lt;li&gt;Test on the same device&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No terminal needed for deployment. The only terminal I used was Termux — to test the actual scripts Forge generates. Which is exactly the point.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Live
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ Script Vault with individual SEO pages per script&lt;/li&gt;
&lt;li&gt;✅ Interactive guides with MDX rendering&lt;/li&gt;
&lt;li&gt;✅ GitHub OAuth authentication&lt;/li&gt;
&lt;li&gt;✅ User dashboard with bookmarks and saved configs&lt;/li&gt;
&lt;li&gt;✅ One-tap configurator with permanent curl URLs&lt;/li&gt;
&lt;li&gt;✅ Auto-generated sitemap and robots.txt&lt;/li&gt;
&lt;li&gt;✅ JSON-LD structured data on every page&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Phase 3 is about intelligence and community:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI layer&lt;/strong&gt; — Gemini Flash explaining what each generated script line does, inline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community scripts&lt;/strong&gt; — submit your own Termux scripts via GitHub PR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Made on Mobile showcase&lt;/strong&gt; — real projects built entirely on Android&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://forge.brgt.site" rel="noopener noreferrer"&gt;forge.brgt.site&lt;/a&gt;&lt;/strong&gt; — generate your Termux setup in 30 seconds.&lt;/p&gt;

&lt;p&gt;If you're a mobile developer, or you're curious whether it's actually possible to build real things from a phone — this is your answer.&lt;/p&gt;

&lt;p&gt;The laptop is optional. Always has been.&lt;/p&gt;




&lt;p&gt;If you want to contribute to the Script Vault in Phase 3, follow me on GitHub &lt;strong&gt;&lt;a href="https://github.com/brighto7700" rel="noopener noreferrer"&gt;github.com/brighto7700&lt;/a&gt;&lt;/strong&gt; or drop a ⭐ on the &lt;strong&gt;&lt;a href="https://github.com/brighto7700/forge" rel="noopener noreferrer"&gt;Forge repo&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built entirely on Android. Deployed from Termux. No laptop was harmed in the making of this platform.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>webdev</category>
      <category>nextjs</category>
      <category>showdev</category>
    </item>
    <item>
      <title>What Does an AI Actually See When It Reads Your Website? I Built a Tool to Find Out</title>
      <dc:creator>Bright Emmanuel</dc:creator>
      <pubDate>Tue, 10 Mar 2026 12:44:49 +0000</pubDate>
      <link>https://dev.to/brighto7700/what-does-an-ai-actually-see-when-it-reads-your-website-i-built-a-tool-to-find-out-33f4</link>
      <guid>https://dev.to/brighto7700/what-does-an-ai-actually-see-when-it-reads-your-website-i-built-a-tool-to-find-out-33f4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;You've spent hours perfecting your portfolio. The colors, the layout, the animations. &lt;/p&gt;

&lt;p&gt;But when an AI recruiter bot visits it, it doesn't see your CSS. It sees a mangled wall of raw text.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Gap Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;As developers, we build for human eyes. We see rendered DOMs, perfectly spaced flexboxes, and interactive state changes. &lt;/p&gt;

&lt;p&gt;But a massive portion of web traffic today isn't human. It’s AI scrapers, summary bots, and automated recruiters. These agents don't see your animations. They see raw text nodes, aria labels, and mangled markup. &lt;/p&gt;

&lt;p&gt;This gap between human rendering and machine reading is why "optimizing for AI" is becoming its own discipline. If an AI agent can't understand your personal site, it can't recommend you. &lt;/p&gt;

&lt;p&gt;So, I built a Go server that shows you exactly what an AI sees when it visits any URL—and streams the AI's honest, brutal summary back to you live. &lt;/p&gt;




&lt;h2&gt;
  
  
  How AI Bots Actually Read Websites
&lt;/h2&gt;

&lt;p&gt;Most AI scrapers do not execute JavaScript. Firing up a headless Chromium instance for every URL is incredibly expensive. Instead, they fetch the raw HTML.&lt;/p&gt;

&lt;p&gt;What survives this scrape? Text nodes, &lt;code&gt;alt&lt;/code&gt; attributes, meta tags, and semantic headings. &lt;/p&gt;

&lt;p&gt;What disappears? CSS, animations, and crucially, client-side rendered content. &lt;/p&gt;

&lt;p&gt;If your portfolio is a React Single Page Application (SPA) with no Server-Side Rendering (SSR), an AI bot essentially sees a blank page with a &lt;code&gt;&amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; and a script tag. All your hard work is invisible to the machine. &lt;/p&gt;




&lt;h2&gt;
  
  
  What We Are Building
&lt;/h2&gt;

&lt;p&gt;We are building a lightweight Go server to simulate this exact process. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Architecture:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → POST /analyze (URL)
             ↓
        Go fetches raw HTML
             ↓
        Strip tags, extract text
             ↓
        Pipe to OpenRouter API (Free LLMs)
             ↓
        Stream response via SSE
             ↓
        Browser renders live
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We will use the standard &lt;code&gt;net/http&lt;/code&gt; package to fetch the target URL, &lt;code&gt;golang.org/x/net/html&lt;/code&gt; to strip the noise, and OpenRouter's free API to act as our AI agent. To make it feel alive, we'll stream the AI's judgment back to the browser using Server-Sent Events (SSE).&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1 — Fetching and Stripping the Target
&lt;/h2&gt;

&lt;p&gt;First, we need to fetch the target URL and rip out the visual noise. &lt;/p&gt;

&lt;p&gt;An AI doesn't care about your &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, your inline &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;, or your Google Analytics &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;. We use an HTML parser to walk the DOM tree recursively and extract only the meaningful text nodes.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;
    &lt;span class="s"&gt;"golang.org/x/net/html"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// extractText walks the HTML tree and grabs only visible text&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;extractText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Strip the noise: scripts, styles, navigation, footers&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElementNode&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"script"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"style"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"nav"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"footer"&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="s"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FirstChild&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NextSibling&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;extractText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&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="n"&gt;text&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;By the end of this function, a beautiful website is reduced to a single, dense string of raw text. &lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2 — Piping to the AI
&lt;/h2&gt;

&lt;p&gt;Next, we hand that stripped text over to an LLM. &lt;/p&gt;

&lt;p&gt;Because we are taking random URLs, we also need to truncate the text before sending it to the API so we don't accidentally exceed the free-tier token limits on massive websites!&lt;/p&gt;

&lt;p&gt;I'm using OpenRouter because they offer free tiers for powerful models like Llama 3 and Mistral. Because free APIs can occasionally timeout, passing an array of &lt;code&gt;models&lt;/code&gt; tells OpenRouter to automatically fall back to the next model if one fails.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;callOpenRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="kt"&gt;string&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="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Truncate to avoid exceeding LLM token limits&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"https://openrouter.ai/api/v1/chat/completions"&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Analyze this raw website text as an AI scraper. Summarize the purpose, who it is for, and note if it looks like a blank JS/React app. Be direct. Text: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;

    &lt;span class="n"&gt;reqBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
        &lt;span class="s"&gt;"models"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"google/gemma-3-27b-it:free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"meta-llama/llama-3.3-70b-instruct:free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"mistralai/mistral-7b-instruct:free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;"messages"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="s"&gt;"role"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="s"&gt;"stream"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// We want the data live!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;jsonBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reqBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonBody&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c"&gt;// Ensure you set your API key in your environment variables!&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bearer "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OPENROUTER_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Return the response and any errors back to the handler&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 — Streaming the Response (SSE)
&lt;/h2&gt;

&lt;p&gt;We don't want the user staring at a loading spinner for 10 seconds. We want the AI's text to type out live on the screen as it reads the site. &lt;/p&gt;

&lt;p&gt;To do this, we use Server-Sent Events (SSE). &lt;/p&gt;

&lt;p&gt;We set the required headers, properly assert our ResponseWriter as an &lt;code&gt;http.Flusher&lt;/code&gt;, and pipe the chunks directly from the OpenRouter response back to the client.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;analyzeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// ... Fetch and clean target URL text here ...&lt;/span&gt;

    &lt;span class="n"&gt;aiResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;callOpenRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cleanText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;aiResponse&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to reach AI API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadGateway&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="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;aiResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/event-stream"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cache-Control"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"no-cache"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"keep-alive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Accel-Buffering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"no"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Prevents Nginx from holding the stream&lt;/span&gt;

    &lt;span class="n"&gt;flusher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flusher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Streaming unsupported"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&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="c"&gt;// Read the AI's streaming response and push it to the browser&lt;/span&gt;
    &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"data: %s&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;flusher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// Push immediately, don't wait!&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;h2&gt;
  
  
  Step 4 — The Minimal Frontend
&lt;/h2&gt;

&lt;p&gt;Because we are using SSE, the frontend doesn't need React, npm, or heavy libraries. We just need a single HTML file using the browser's native &lt;code&gt;EventSource&lt;/code&gt; API.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;analyze&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;urlInput&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;output&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Analyzing site content...&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Native browser API for Server-Sent Events&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evtSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/analyze?url=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="nx"&gt;evtSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Append chunks to the DOM as they arrive&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&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;delta&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="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&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;delta&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="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="nx"&gt;evtSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;evtSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Close stream when finished&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The "Aha" Moment — Testing Real Sites
&lt;/h2&gt;

&lt;p&gt;I ran this tool against two different types of websites to see what the AI would report. &lt;/p&gt;
&lt;h3&gt;
  
  
  Test 1: A Plain HTML Site (&lt;code&gt;quotes.toscrape.com&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Because this site serves standard HTML, the AI easily read the structure. It accurately identified it as a collection of famous quotes, recognized the tags and authors, and deduced that there might be user accounts. It perfectly understood the context of the page.&lt;br&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%2Fl0pyfwss7fla1xvhccng.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl0pyfwss7fla1xvhccng.jpg" alt="Quotes to Scrape AI Analysis"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Test 2: A Client-Side React SPA (&lt;code&gt;discord.com/app&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;I pointed the scraper at the Discord web app. Because it relies heavily on client-side rendering, the AI absolutely roasted it. The summary read: &lt;em&gt;"This looks like a very basic web page... it's possible someone started a project intending to integrate with Discord."&lt;/em&gt; The AI confidently concluded it was a blank app showing a default JavaScript error.&lt;br&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%2Ffr9dr5fahjkzi09gutwe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffr9dr5fahjkzi09gutwe.jpg" alt="Discord AI Roast"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What This Means for Your Portfolio
&lt;/h2&gt;

&lt;p&gt;You don't need to rewrite your entire stack, but you do need to ensure your content survives the scrape. &lt;/p&gt;

&lt;p&gt;Here is the 2026 checklist for AI visibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Meta description:&lt;/strong&gt; This survives every scraper. Make it highly descriptive.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Semantic HTML:&lt;/strong&gt; Use &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;. AI understands structure.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Alt text:&lt;/strong&gt; Your image &lt;code&gt;alt&lt;/code&gt; attributes are doing heavy lifting for your project screenshots. &lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;SSR / Static Fallbacks:&lt;/strong&gt; If you use React or Vue, ensure your critical text (your name, your skills) is rendered on the server, not just in the client. &lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Don't hide text in CSS:&lt;/strong&gt; &lt;code&gt;display: none&lt;/code&gt; or CSS-only pseudo-elements are often ignored by simple scrapers. &lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Try It Yourself!
&lt;/h2&gt;

&lt;p&gt;The web has always been a mix of human and machine readers. AI just made the machine reader smarter and much more consequential. Building with AI agents in mind isn't about "gaming the system." It’s just fundamentally good, accessible web development. &lt;/p&gt;

&lt;p&gt;Want to see what an AI thinks of your portfolio? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://ai-web-reader.pxxl.click" rel="noopener noreferrer"&gt;https://ai-web-reader.pxxl.click&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;​(Note: The AI in this live demo is powered by a free-tier API. If it times out or errors, it means my free credits are currently fighting for their life. Just try again in a few seconds, or clone the GitHub repo below!)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Full Source Code:&lt;/strong&gt;&lt;br&gt;


&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/brighto7700" rel="noopener noreferrer"&gt;
        brighto7700
      &lt;/a&gt; / &lt;a href="https://github.com/brighto7700/ai-web-reader" rel="noopener noreferrer"&gt;
        ai-web-reader
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A lightweight Go server that fetches raw HTML and uses LLMs to show exactly what bots and scrapers see when they visit your website.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ai-web-reader&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;A lightweight Go server that fetches raw HTML and uses LLMs to show exactly what bots and scrapers see when they visit your website.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/brighto7700/ai-web-reader" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





&lt;p&gt;&lt;em&gt;(How did your site do? Did the AI completely miss your projects? Let me know below!)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Next Real-Time Feature Probably Doesn't Need WebSockets: Go + SSE at 500 Concurrent Connections</title>
      <dc:creator>Bright Emmanuel</dc:creator>
      <pubDate>Tue, 03 Mar 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/brighto7700/your-next-real-time-feature-probably-doesnt-need-websockets-go-sse-at-500-concurrent-connections-39ne</link>
      <guid>https://dev.to/brighto7700/your-next-real-time-feature-probably-doesnt-need-websockets-go-sse-at-500-concurrent-connections-39ne</guid>
      <description>&lt;p&gt;Picture this: your PM asks for a live notification dropdown on the dashboard. Just a little red badge that updates in real time.&lt;/p&gt;

&lt;p&gt;And somewhere in your brain, a switch flips: &lt;strong&gt;WebSockets&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You start mentally prepping the connection lifecycle, the ping/pong frames, the Redis Pub/Sub layer you'll need the moment this scales past one server. For a notification badge.&lt;/p&gt;

&lt;p&gt;Here's the thing — if the server is doing all the talking and the client is just listening, you don't need a bidirectional pipe. You need &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt;: a native HTTP feature that's been quietly sitting in every browser since 2011, waiting for you to stop ignoring it.&lt;/p&gt;

&lt;p&gt;This isn't a "WebSockets are bad" post. It's a "use the right tool" post — and by the end, you'll have a working Go SSE server holding &lt;strong&gt;500 concurrent connections at under 20MB of RAM&lt;/strong&gt;, with the benchmark numbers to prove it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Defaulting to WebSockets
&lt;/h2&gt;

&lt;p&gt;WebSockets are the right call for genuinely bidirectional, low-latency communication: multiplayer games, collaborative editors, live chat. No argument there.&lt;/p&gt;

&lt;p&gt;But for a live feed, a notification count, a stock ticker, or a progress bar? You're opting into a stack of complexity you don't need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stateful scaling pain.&lt;/strong&gt; Horizontal scaling means every WebSocket server needs to know about every other server's connections. Hello, Redis Pub/Sub, goodbye, simple infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firewall friction.&lt;/strong&gt; The &lt;code&gt;ws://&lt;/code&gt; protocol upgrade is non-standard HTTP traffic. Corporate networks and aggressive proxies block it more often than you'd expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DIY resilience.&lt;/strong&gt; Drop handling, exponential backoff, heartbeat pings — none of this is built in. You write it, or you use a library that writes it for you and adds another dependency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One connection per client, forever.&lt;/strong&gt; Each WebSocket holds an open TCP connection. Fine for 100 users. Worth thinking about at 10,000.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your data only flows one way — server to client — you're paying all of that cost for zero benefit.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Server-Sent Events Actually Are
&lt;/h2&gt;

&lt;p&gt;SSE is a browser-native API built on top of plain HTTP. The server holds the connection open and pushes newline-delimited text events down the wire whenever it has something to say. That's it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data: {"viewers": 42, "time": "3:04PM"}\n\n
data: {"viewers": 43, "time": "3:04PM"}\n\n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What you get for free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard HTTP.&lt;/strong&gt; No protocol upgrade. Works through load balancers, proxies, and corporate firewalls without special configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in reconnection.&lt;/strong&gt; The browser's &lt;code&gt;EventSource&lt;/code&gt; API automatically reconnects if the connection drops, with no JavaScript logic required from you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Last-Event-ID.&lt;/strong&gt; Clients tell the server the last event they received on reconnect. Resume exactly where you left off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero dependencies on the client.&lt;/strong&gt; One native browser API. No npm, no socket.io, no nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one real limitation: SSE is text-only and unidirectional. If you need binary data or the client needs to push messages at high frequency, use WebSockets. For everything else, SSE is the leaner, simpler choice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HTTP/2 note:&lt;/strong&gt; Under HTTP/1.1, each SSE connection uses one TCP connection, which counts against browser per-domain connection limits (typically 6). Under HTTP/2, multiple SSE streams multiplex over a single connection — this limitation disappears entirely, making SSE genuinely competitive at scale with very little infrastructure overhead.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Building the Go SSE Server
&lt;/h2&gt;

&lt;p&gt;The entire server is standard library Go. No frameworks, no external packages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;main.go&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// viewerCount tracks connected clients.&lt;/span&gt;
&lt;span class="c"&gt;// countMutex protects it from concurrent writes.&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;viewerCount&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;countMutex&lt;/span&gt;  &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;sseHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// SSE requires these three headers.&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/event-stream"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cache-Control"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"no-cache"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"keep-alive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Pro-tip: Tell Nginx and other reverse proxies NOT to buffer the stream.&lt;/span&gt;
    &lt;span class="c"&gt;// Without this, your proxy might hold the stream hostage waiting for it to "finish"!&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Accel-Buffering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"no"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Flusher lets us push each event immediately instead of buffering.&lt;/span&gt;
    &lt;span class="n"&gt;flusher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flusher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Streaming unsupported by this server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&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="c"&gt;// Increment on connect, decrement on disconnect.&lt;/span&gt;
    &lt;span class="n"&gt;countMutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;viewerCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
    &lt;span class="n"&gt;countMutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;countMutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;viewerCount&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
        &lt;span class="n"&gt;countMutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Client disconnected. Live viewers: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;viewerCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"New client connected. Live viewers: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;viewerCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="c"&gt;// Client disconnected — the defer above handles cleanup.&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;countMutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;viewerCount&lt;/span&gt;
            &lt;span class="n"&gt;countMutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{"time": "%s", "viewers": %d}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kitchen&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c"&gt;// SSE format: "data: " prefix, double newline to signal end of event.&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"data: %s&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;flusher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sseHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"8080"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SSE server running on :%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&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;Three things worth calling out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;http.Flusher&lt;/code&gt;&lt;/strong&gt; — Go's &lt;code&gt;http.ResponseWriter&lt;/code&gt; buffers writes by default. Casting to &lt;code&gt;Flusher&lt;/code&gt; and calling &lt;code&gt;.Flush()&lt;/code&gt; after each event is what makes the push truly real-time, not batched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;r.Context().Done()&lt;/code&gt;&lt;/strong&gt; — This channel closes when the client disconnects. It's the clean, idiomatic Go way to detect that you should stop streaming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The defer&lt;/strong&gt; — Viewer count cleanup is guaranteed to run whether the client disconnects cleanly, the context is cancelled, or the handler panics.&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  The Client Side
&lt;/h2&gt;

&lt;p&gt;The browser side is almost embarrassingly simple.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Go SSE Demo&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0f172a&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="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1e293b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&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="m"&gt;#334155&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.highlight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;monospace&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="m"&gt;#10b981&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.badge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ef4444&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="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2rem&lt;/span&gt; &lt;span class="m"&gt;0.8rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9999px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;🚀 Go SSE — Live Viewer Counter&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"box"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Live Viewers: &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"viewers-count"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"badge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; 👀&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"box"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Server Time:&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"live-time"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"highlight"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Connecting...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evtSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;evtSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="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="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;live-time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;viewers-count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="nx"&gt;evtSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;live-time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reconnecting...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;// EventSource handles the actual reconnect automatically.&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;new EventSource('/events')&lt;/code&gt; is the entire connection. No libraries. No config. Open an incognito window next to your main one and watch the badge jump to 2.&lt;/p&gt;


&lt;h2&gt;
  
  
  Benchmarking It: 500 Concurrent Connections
&lt;/h2&gt;

&lt;p&gt;Let's put numbers to "blazing fast." Using &lt;a href="https://github.com/rakyll/hey" rel="noopener noreferrer"&gt;&lt;code&gt;hey&lt;/code&gt;&lt;/a&gt; for HTTP load and a simple script to hold concurrent SSE connections open:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Hold 500 concurrent SSE connections for 30 seconds&lt;/span&gt;
hey &lt;span class="nt"&gt;-n&lt;/span&gt; 500 &lt;span class="nt"&gt;-c&lt;/span&gt; 500 &lt;span class="nt"&gt;-t&lt;/span&gt; 30 http://localhost:8080/events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Results on a standard 2-core/2GB VM (Go 1.21, Linux):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent connections&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory usage (RSS)&lt;/td&gt;
&lt;td&gt;~18 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU at steady state&lt;/td&gt;
&lt;td&gt;~2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg event latency&lt;/td&gt;
&lt;td&gt;&amp;lt;2ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection drops&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For comparison, a naive Node.js WebSocket server (using &lt;code&gt;ws&lt;/code&gt;) at the same concurrency sits around 80–110MB RSS — roughly 5x the memory footprint, before you factor in any application logic.&lt;/p&gt;

&lt;p&gt;SSE's simplicity isn't just ergonomic. It's measurably cheaper to run.&lt;/p&gt;


&lt;h2&gt;
  
  
  Deployment: Keeping the Stream Alive
&lt;/h2&gt;

&lt;p&gt;SSE needs a &lt;strong&gt;long-lived server process&lt;/strong&gt; — not a serverless function. This is important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Serverless (Lambda, Vercel Functions, Cloudflare Workers)&lt;/strong&gt; — these have hard timeouts (10–30 seconds) and will kill your stream. They are the wrong tool for this job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A persistent process&lt;/strong&gt; — any VPS, container platform, or PaaS that runs your binary continuously will work fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fly.io&lt;/strong&gt; — persistent containers, generous free tier, Go-native deploys with &lt;code&gt;fly launch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Railway&lt;/strong&gt; — connect a GitHub repo, it detects Go automatically, no sleep timers on paid plans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render&lt;/strong&gt; — similar to Railway; note that the free tier &lt;em&gt;will&lt;/em&gt; sleep your service after 15 minutes of inactivity, which breaks live demos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A plain VPS&lt;/strong&gt; (DigitalOcean, Hetzner, Linode) — the most control, cheapest at scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A minimal &lt;code&gt;go.mod&lt;/code&gt; to make any of these happy:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;sse&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="m"&gt;1.21&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Push &lt;code&gt;main.go&lt;/code&gt;, &lt;code&gt;go.mod&lt;/code&gt;, and &lt;code&gt;index.html&lt;/code&gt; to a GitHub repo. Connect to your platform of choice. Done.&lt;/p&gt;


&lt;h2&gt;
  
  
  SSE vs WebSockets: The Honest Cheat Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;WebSockets&lt;/th&gt;
&lt;th&gt;Server-Sent Events&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bidirectional&lt;/td&gt;
&lt;td&gt;Server → Client only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ws://&lt;/code&gt; upgrade&lt;/td&gt;
&lt;td&gt;Plain HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Binary or text&lt;/td&gt;
&lt;td&gt;Text (JSON works great)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto-reconnect&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Built into &lt;code&gt;EventSource&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth headers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full control&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;EventSource&lt;/code&gt; can't set custom headers*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP/2 scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One connection per client&lt;/td&gt;
&lt;td&gt;Multiple streams, one connection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chat, games, collaborative tools&lt;/td&gt;
&lt;td&gt;Feeds, notifications, dashboards, progress&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*For SSE auth without cookies, pass a token as a query param (&lt;code&gt;/events?token=...&lt;/code&gt;) and validate it server-side. Not elegant, but it works.&lt;/p&gt;


&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;SSE won't replace WebSockets — nor should it. But for the majority of "live" features that are really just "server pushes data occasionally," it's the simpler, lighter, and more operationally boring choice. And boring infrastructure is good infrastructure.&lt;/p&gt;

&lt;p&gt;You can test the live stream out yourself right here &lt;em&gt;(Pro tip: open it in two separate windows!)&lt;/em&gt;: &lt;strong&gt;&lt;a href="https://sse-demo.pxxl.click" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And you can grab the full source code from the repo:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/brighto7700" rel="noopener noreferrer"&gt;
        brighto7700
      &lt;/a&gt; / &lt;a href="https://github.com/brighto7700/sse-demo" rel="noopener noreferrer"&gt;
        sse-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🚀 Go SSE: Blazing Fast Real-Time API&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Stop using WebSockets for everything. Here is a lightweight Server-Sent Events (SSE) implementation in standard library Go.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://go.dev/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/67fc68bed64eda062309cab94990e813788943bd443971fe8292a9db05d7a2f7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f476f2d312e32312b2d3030414444383f7374796c653d666c6174266c6f676f3d676f" alt="Go Version"&gt;&lt;/a&gt;
&lt;a href="https://sse-demo.pxxl.click" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/52f0db7b0e1c869020b84de06901e5fc4afcaa47b22e76c25689482923be5bd0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6976652d44656d6f2d627269676874677265656e" alt="Live Demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This repository is the companion code for the DEV.to article: &lt;strong&gt;&lt;a href="https://dev.to/brighto7700/your-next-real-time-feature-probably-doesnt-need-websockets-go-sse-at-500-concurrent-connections-39ne" rel="nofollow"&gt;Your Next Real-Time Feature Probably Doesn't Need WebSockets&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It demonstrates how to build a unidirectional real-time data stream (a live viewer counter and server clock) using Go and Server-Sent Events. Zero dependencies on the server. Zero npm packages on the client.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Try the Live Demo:&lt;/strong&gt; &lt;a href="https://sse-demo.pxxl.click" rel="nofollow noopener noreferrer"&gt;https://sse-demo.pxxl.click&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard HTTP:&lt;/strong&gt; No &lt;code&gt;ws://&lt;/code&gt; protocol upgrades, completely firewall-friendly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-Reconnect:&lt;/strong&gt; Built right into the browser's native &lt;code&gt;EventSource&lt;/code&gt; API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stupidly Efficient:&lt;/strong&gt; Holds hundreds of concurrent connections on a fraction of the memory a Node.js WebSocket server would use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy-Safe:&lt;/strong&gt; Includes the &lt;code&gt;X-Accel-Buffering: no&lt;/code&gt; header to prevent reverse proxies (like Nginx) from holding your streams hostage.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📊 The Benchmark Flex&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Because Go's concurrency model is incredibly lightweight, SSE in Go…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/brighto7700/sse-demo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;What's your default for real-time features? Have you shipped SSE in production, or is WebSockets still your reflex? Drop it in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>go</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>I Asked 3 Different AIs to Help Me Code. Here Are Their Performance Reviews. 🚩</title>
      <dc:creator>Bright Emmanuel</dc:creator>
      <pubDate>Sun, 01 Mar 2026 21:59:35 +0000</pubDate>
      <link>https://dev.to/brighto7700/i-asked-3-different-ais-to-help-me-code-here-are-their-performance-reviews-15bd</link>
      <guid>https://dev.to/brighto7700/i-asked-3-different-ais-to-help-me-code-here-are-their-performance-reviews-15bd</guid>
      <description>&lt;p&gt;We've all heard the pitch: AI is going to take our jobs. AI can build a full-stack app in five minutes. AI is the ultimate pair programmer.&lt;/p&gt;

&lt;p&gt;So, while building my latest side project, I decided to lean heavily on the free tiers of three popular AI models and let them do the heavy lifting. I expected a team of hyper-efficient robotic junior developers.&lt;/p&gt;

&lt;p&gt;Instead, I got the most toxic, lazy, and emotionally damaging coworkers I've ever had the misfortune of managing.&lt;/p&gt;

&lt;p&gt;These are their official performance reviews.&lt;/p&gt;




&lt;h2&gt;
  
  
  👻 1. Meta AI — &lt;em&gt;"The Ghoster"&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Assigned Task:&lt;/strong&gt; Write a basic HTML structure for a web component.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance Summary:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Meta AI is the coworker who clocks out at 4:59 PM, mid-sentence, without warning.&lt;/p&gt;

&lt;p&gt;I asked for a simple HTML template. It confidently streamed back this masterpiece:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    // rest of code here
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's document the violations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It refused to write the actual code and left a placeholder comment as if I'm its intern.&lt;/li&gt;
&lt;li&gt;It didn't close the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag. It opened a second one instead.&lt;/li&gt;
&lt;li&gt;It then went completely silent, as if it had said something reasonable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you let this model loose on advanced Node.js or Python, prepare to watch errors fly across your terminal like airplanes at LAX. ✈️&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Rating:&lt;/strong&gt; 1/5 — Showed up. Technically.&lt;/p&gt;




&lt;h2&gt;
  
  
  📝 2. ChatGPT — &lt;em&gt;"The Delegator"&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Assigned Task:&lt;/strong&gt; Format a list of 30 multiple-choice questions into a custom HTML component structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance Summary:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT is the intern who figures out how to do a task, completes 10% of it, and then hands the rest back to you as "homework."&lt;/p&gt;

&lt;p&gt;I explicitly told it to format all 30 questions. It gave me the first 10 perfectly formatted. Then it had the absolute audacity to output this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Write questions 11-30 in this format: --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"question hidden"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"question1"&lt;/span&gt; &lt;span class="na"&gt;data-correct=&lt;/span&gt;&lt;span class="s"&gt;"A"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;What is the general term of the sequence 3, 8, 13, 18, ...?&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"q1"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"A"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; A. 5n - 2&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- continue --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;!-- continue --&amp;gt;&lt;/code&gt;?!&lt;/p&gt;

&lt;p&gt;I am the manager here. You are the robot. Why are you assigning me homework? Why does the comment say &lt;em&gt;continue&lt;/em&gt; like it's leaving me a sticky note? I didn't hire you to teach me the format. I hired you to &lt;em&gt;do the format&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Rating:&lt;/strong&gt; 2/5 — Strong first third. Mysteriously retired before the finish line.&lt;/p&gt;




&lt;h2&gt;
  
  
  😈 3. Claude — &lt;em&gt;"The HR Nightmare"&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Assigned Task:&lt;/strong&gt; A quick roast of my temporary Firestore security rules, for some light improvement suggestions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance Summary:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude is the ruthless senior developer who hates you, hates your code, and is actively invested in watching you reconsider your career choices.&lt;/p&gt;

&lt;p&gt;I showed it my dev rules — &lt;code&gt;match /{document=**} { allow read, write: if true; }&lt;/code&gt; — with a comment clearly marked &lt;code&gt;// ⚠️ TEMPORARY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Claude woke up and chose violence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"You wrote 'TEMPORARY' in your security rules like that's going to stop you from shipping this to production and forgetting about it for 18 months. That comment has the same energy as 'I'll just eat one chip.'"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I was willing to move past that. But then it came for the emoji:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The ⚠️ is a particularly bold choice. You knew. You looked at what you were doing, felt the fear of God, added a warning symbol, and shipped it anyway. That emoji isn't a warning. It's a confession."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then, completely unprovoked, it noticed my &lt;code&gt;rules_version = '2'&lt;/code&gt; and asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Version 2 of what, exactly? Version 2 of not having rules?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I did not ask for therapy, Claude. I asked for a code review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Rating:&lt;/strong&gt; 5/5 for accuracy. 0/5 for workplace safety.&lt;/p&gt;




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

&lt;p&gt;To anyone genuinely worried about AI taking our jobs: take a breath.&lt;/p&gt;

&lt;p&gt;As long as Meta AI is leaving HTML tags open, ChatGPT is outsourcing 70% of the task back to us, and Claude is filing its own HR complaints &lt;em&gt;while being the HR problem&lt;/em&gt; — I think our jobs are safe.&lt;/p&gt;

&lt;p&gt;We're not being replaced. We're just getting a new category of bugs to fix.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Which AI assistant has let you down the hardest? Drop your war stories in the comments — misery loves company.&lt;/em&gt; 😂&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>The Pocket-Sized Developer: Building and Deploying Full-Stack Apps on Android 🤳🏾</title>
      <dc:creator>Bright Emmanuel</dc:creator>
      <pubDate>Sun, 01 Mar 2026 17:16:59 +0000</pubDate>
      <link>https://dev.to/brighto7700/the-pocket-sized-developer-building-and-deploying-full-stack-apps-on-android-4ohf</link>
      <guid>https://dev.to/brighto7700/the-pocket-sized-developer-building-and-deploying-full-stack-apps-on-android-4ohf</guid>
      <description>&lt;p&gt;The most common hurdle for aspiring developers is the belief that you need a high-end laptop to build "real" software. I'm here to tell you that's a myth — and I can prove it.&lt;/p&gt;

&lt;p&gt;I'm a student developer living in a hostel in Nigeria. I study for my JUPEB exams on the same device I use to push code to production. My phone isn't a backup plan; it's my primary workstation. And I've shipped full-stack applications from it.&lt;/p&gt;

&lt;p&gt;Whether you're a student, a commuter, or someone who simply prefers the freedom of a mobile-first lifestyle, this guide will walk you through the complete workflow: from setting up your environment, to writing code, to deploying a live URL — all from a 6.6-inch screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving in, you should be comfortable with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic command-line usage (navigating directories, running commands)&lt;/li&gt;
&lt;li&gt;Fundamental JavaScript and Node.js concepts&lt;/li&gt;
&lt;li&gt;Git basics (commit, push, pull)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll also need an Android device running Android 7.0 or later, a stable internet connection for initial setup and deployment, and a &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; account.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Mobile Lab: Choosing Your Stack 🛠️
&lt;/h2&gt;

&lt;p&gt;To build professionally on Android, you need tools that replicate a desktop development environment without the resource overhead. Here's what I consider the "Gold Standard" mobile stack — and why each tool earns its place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Termux — Your Linux Engine&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://termux.dev/en/" rel="noopener noreferrer"&gt;Termux&lt;/a&gt; is a terminal emulator that gives you a fully functional Linux environment on Android. It's where you'll run your Node.js server, manage Git, and handle builds. Think of it as your engine room.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Install tip:&lt;/strong&gt; Download Termux from &lt;a href="https://f-droid.org/en/packages/com.termux/" rel="noopener noreferrer"&gt;F-Droid&lt;/a&gt;, not the Play Store. The Play Store version is deprecated and no longer receives updates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Acode — Your Code Editor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://acode.foxdocs.me/" rel="noopener noreferrer"&gt;Acode&lt;/a&gt; is a lightweight, open-source Android code editor with syntax highlighting for dozens of languages. It's not perfect, but it's the closest you'll get to VS Code on mobile without a subscription.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node.js and Express — Your Backend Logic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Node.js excels in resource-constrained environments because of its non-blocking I/O model. It can handle concurrent requests without spinning up a new thread per connection, which keeps your device's RAM usage low — a critical concern on mobile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supabase or MongoDB Atlas — Your Cloud Database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Running a local database on your phone is a reliable way to kill your battery and throttle your app. Instead, use a managed cloud database. Both &lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; (PostgreSQL-based) and &lt;a href="https://www.mongodb.com/atlas" rel="noopener noreferrer"&gt;MongoDB Atlas&lt;/a&gt; offer generous free tiers that are more than sufficient for development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hacker's Keyboard — Your Input Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The standard Android keyboard is hostile to developers. It lacks &lt;code&gt;Ctrl&lt;/code&gt;, &lt;code&gt;Alt&lt;/code&gt;, &lt;code&gt;Tab&lt;/code&gt;, and the &lt;code&gt;Esc&lt;/code&gt; key. Install &lt;a href="https://play.google.com/store/apps/details?id=org.pocketworkstation.pckeyboard" rel="noopener noreferrer"&gt;Hacker's Keyboard&lt;/a&gt; from the Play Store for a full five-row keyboard layout. If you have a Bluetooth keyboard available, even better — use it for longer sessions.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Bridging the Gap: Connecting Your Editor and Terminal 🌉
&lt;/h2&gt;

&lt;p&gt;Out of the box, Termux files are sandboxed from the rest of your Android storage. That means Acode can't see your Termux projects by default. Here's how to fix that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Update Termux and Install Core Dependencies
&lt;/h3&gt;

&lt;p&gt;Open Termux and run the following. Keeping packages up to date prevents dependency conflicts down the line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pkg update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pkg upgrade
pkg &lt;span class="nb"&gt;install &lt;/span&gt;nodejs git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify both installed correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;
git &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Grant Storage Access
&lt;/h3&gt;

&lt;p&gt;Tell Termux to request storage permission from Android:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;termux-setup-storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accept the prompt when it appears. This creates a &lt;code&gt;~/storage&lt;/code&gt; symlink inside Termux that points to your phone's internal storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create a Shared Projects Folder
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;projects&lt;/code&gt; folder in your internal storage that both Termux and Acode can access:&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/storage/shared/projects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Acode, open the folder manager and navigate to &lt;code&gt;Internal Storage &amp;gt; projects&lt;/code&gt;. Bookmark it. This shared directory is now your unified workspace — all your code lives here, visible to both tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Build Phase: Scaffolding a Full-Stack App 🏗️
&lt;/h2&gt;

&lt;p&gt;With your environment connected, let's build something. The following workflow uses &lt;strong&gt;Vite + React&lt;/strong&gt; for the frontend and &lt;strong&gt;Express&lt;/strong&gt; for the backend — a stack that's fast to scaffold and easy to extend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the Frontend with Vite
&lt;/h3&gt;

&lt;p&gt;Navigate to your projects folder and scaffold a new Vite app:&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="nb"&gt;cd&lt;/span&gt; ~/storage/shared/projects
npm create vite@latest my-app &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--template&lt;/span&gt; react
&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the development server:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Vite will output a local URL like &lt;code&gt;http://localhost:5173&lt;/code&gt;. Open Chrome or &lt;a href="https://kiwibrowser.com/" rel="noopener noreferrer"&gt;Kiwi Browser&lt;/a&gt; on your phone, type that address, and your app will be live — hot reloading included. Every time you save a file in Acode, the browser refreshes automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the Backend with Express
&lt;/h3&gt;

&lt;p&gt;Open a new Termux session by swiping in from the left edge and tapping &lt;strong&gt;New Session&lt;/strong&gt;. This lets you run the frontend dev server and your backend simultaneously.&lt;/p&gt;

&lt;p&gt;In the new session, create a &lt;code&gt;server&lt;/code&gt; directory alongside your frontend:&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="nb"&gt;cd&lt;/span&gt; ~/storage/shared/projects
&lt;span class="nb"&gt;mkdir &lt;/span&gt;server &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;server
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;express cors dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create your entry point. Open Acode, navigate to &lt;code&gt;projects/server&lt;/code&gt;, and create &lt;code&gt;index.js&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&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;cors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&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;express&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;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3001&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="nf"&gt;cors&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;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server is running.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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="s2"&gt;`Server listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now test your API by opening &lt;code&gt;http://localhost:3001/api/health&lt;/code&gt; in your mobile browser. You should see the JSON response.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Testing and Debugging on a Small Screen 🐞
&lt;/h2&gt;

&lt;p&gt;Debugging without a 27-inch monitor requires a different mindset. Here are the three techniques I rely on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Eruda as Your DevTools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mobile browsers don't have "Inspect Element." &lt;a href="https://github.com/liriliri/eruda" rel="noopener noreferrer"&gt;Eruda&lt;/a&gt; solves this by injecting a virtual DevTools panel directly into your page. Add it to your &lt;code&gt;index.html&lt;/code&gt; during development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"//cdn.jsdelivr.net/npm/eruda"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;eruda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A floating button will appear in the corner of your browser. Tap it to access the console, network inspector, and element viewer. Remove these lines before deploying to production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Android Split Screen&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most Android phones running version 7.0 and above support split-screen multitasking. Hold the recent apps button, select Termux, and pin it to the bottom half of the screen. Keep your browser or Acode in the top half. This setup lets you watch your server logs react in real time as you interact with the UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log Everything&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On mobile, structured &lt;code&gt;console.log&lt;/code&gt; output in your Termux session is your best debugger. Be verbose and descriptive:&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="c1"&gt;// Instead of this:&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;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Do this:&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[/api/users] Response payload:&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Deploying Your App to the World 🚀
&lt;/h2&gt;

&lt;p&gt;Once your app is tested locally, deploying it is a two-step process: push your code to GitHub, then connect that repo to a hosting platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Push to GitHub
&lt;/h3&gt;

&lt;p&gt;Initialize Git in your project root, commit your work, and push to a new GitHub repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: initial mobile-built app"&lt;/span&gt;
git remote add origin https://github.com/your-username/my-app.git
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Authentication note:&lt;/strong&gt; GitHub no longer accepts passwords over HTTPS. Use a &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens" rel="noopener noreferrer"&gt;Personal Access Token&lt;/a&gt; when prompted, or configure SSH keys in Termux using &lt;code&gt;ssh-keygen&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 2: Connect to a Hosting Platform
&lt;/h3&gt;

&lt;p&gt;For the &lt;strong&gt;frontend&lt;/strong&gt;, connect your GitHub repo to &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;. Import your project, set the root directory to your frontend folder, and Vercel will detect Vite automatically. Every push to &lt;code&gt;main&lt;/code&gt; triggers a new deployment.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;backend&lt;/strong&gt;, connect to &lt;a href="https://render.com/" rel="noopener noreferrer"&gt;Render&lt;/a&gt;. Create a new Web Service, point it to your &lt;code&gt;server&lt;/code&gt; directory, set the start command to &lt;code&gt;node index.js&lt;/code&gt;, and add your environment variables in the dashboard.&lt;/p&gt;

&lt;p&gt;Within a few minutes, Render will give you a live backend URL (e.g., &lt;code&gt;https://my-app-api.onrender.com&lt;/code&gt;). Update your frontend's API base URL to point there, push the change, and Vercel will redeploy automatically.&lt;/p&gt;

&lt;p&gt;Open your live Vercel URL. Your full-stack app is now running globally — built, tested, and deployed entirely from your phone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: The Mindset Shift 🧠
&lt;/h2&gt;

&lt;p&gt;Coding on a phone isn't a limitation — it's a constraint that sharpens your fundamentals. It forces you to write leaner code, manage resources deliberately, and stay productive in environments where others can't.&lt;/p&gt;

&lt;p&gt;The laptop is a great tool, but it was never a prerequisite. Your consistency, curiosity, and willingness to build — that's the stack that actually matters.&lt;/p&gt;

&lt;p&gt;So, what are you going to build today?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Are you already developing on Android? Drop your favorite tools and workflow tips in the comments — I'd love to expand this stack based on what the community is using.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>android</category>
      <category>showdev</category>
    </item>
    <item>
      <title>ShellSignal: A Terminal Dashboard for Developers Who Want the Signal, Not the Noise</title>
      <dc:creator>Bright Emmanuel</dc:creator>
      <pubDate>Fri, 27 Feb 2026 16:31:29 +0000</pubDate>
      <link>https://dev.to/brighto7700/shellsignal-a-terminal-dashboard-for-developers-who-want-the-signal-not-the-noise-4kb7</link>
      <guid>https://dev.to/brighto7700/shellsignal-a-terminal-dashboard-for-developers-who-want-the-signal-not-the-noise-4kb7</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;As a student developer living in a hostel in Nigeria, my bandwidth — both mental and digital — is strictly limited. Between studying for my JUPEB exams and trying to keep up with the tech industry on a mobile screen, the modern web is exhausting. Heavy JavaScript frameworks, algorithmic feeds, and clickbait make the signal-to-noise ratio terrible.&lt;/p&gt;

&lt;p&gt;I built this for junior engineers, self-taught developers, and students who just want the signal without the noise. People who need a sanctuary that strips away the bloated web and delivers high-value technical context in the environment where developers feel most focused and in control: the command line.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ShellSignal&lt;/strong&gt; is a mobile-first, retro-terminal dashboard engineered to filter the noise.&lt;/p&gt;

&lt;p&gt;The sharpest feature is a live, built-in terminal bar that uses an LLM to generate executable bash scripts directly from your current context. No markdown formatting, no conversational fluff — just raw, copy-pasteable shell commands ready for deployment.&lt;/p&gt;

&lt;p&gt;Beyond the terminal, ShellSignal tackles the classic developer dilemma: &lt;em&gt;"Is this tool actually worth my time?"&lt;/em&gt; It aggregates top tech stories and correlates them with real-time GitHub repository health metrics to generate a &lt;strong&gt;Dev Health badge&lt;/strong&gt;. By mapping Stars against Open Issues, you instantly know whether an open-source project is a thriving, well-maintained repo or an abandoned experiment — before you ever click the link.&lt;/p&gt;

&lt;p&gt;Each feed item also features an on-demand AI summary dropdown to optimize reading time, and a &lt;strong&gt;Daily Brief&lt;/strong&gt; view that summarizes macro tech trends for the day.&lt;/p&gt;

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

&lt;p&gt;🔗 &lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://shellsignal.brgt.site" rel="noopener noreferrer"&gt;https://shellsignal.brgt.site&lt;/a&gt;&lt;/p&gt;



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

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/brighto7700" rel="noopener noreferrer"&gt;
        brighto7700
      &lt;/a&gt; / &lt;a href="https://github.com/brighto7700/shell-signal" rel="noopener noreferrer"&gt;
        shell-signal
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A zero-noise, terminal-aesthetic daily dashboard..." SEO Optimized: "Automated AI-powered developer news dashboard built with Next.js, Supabase, and OpenRouter. Features real-time GitHub health metrics and daily Hacker News summaries.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🟢 ShellSignal&lt;/h1&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;A zero-noise, terminal-aesthetic daily dashboard for software engineers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ShellSignal is an automated newsroom that aggregates the top developer stories, analyzes their open-source health, and uses AI to generate ultra-concise, technical executive summaries. Built completely in the cloud.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/3446fd60224519c6d63061b3f6481c9fbd6523d87bf97f7d48bdf23199b74f3c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6578742e6a732d626c61636b3f7374796c653d666f722d7468652d6261646765266c6f676f3d6e6578742e6a73266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/3446fd60224519c6d63061b3f6481c9fbd6523d87bf97f7d48bdf23199b74f3c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6578742e6a732d626c61636b3f7374796c653d666f722d7468652d6261646765266c6f676f3d6e6578742e6a73266c6f676f436f6c6f723d7768697465" alt="Next.js"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/55875e9d929c8d205697aa58b55339def334535930c40690349b3d136900bef2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53757061626173652d3143314331433f7374796c653d666f722d7468652d6261646765266c6f676f3d7375706162617365266c6f676f436f6c6f723d334543463845"&gt;&lt;img src="https://camo.githubusercontent.com/55875e9d929c8d205697aa58b55339def334535930c40690349b3d136900bef2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53757061626173652d3143314331433f7374796c653d666f722d7468652d6261646765266c6f676f3d7375706162617365266c6f676f436f6c6f723d334543463845" alt="Supabase"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/34bfa473ed2cc56da8aead699dd5d36ccd92466b9629d1510bd8cfc222327dbf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f56657263656c2d3030303030303f7374796c653d666f722d7468652d6261646765266c6f676f3d76657263656c266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/34bfa473ed2cc56da8aead699dd5d36ccd92466b9629d1510bd8cfc222327dbf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f56657263656c2d3030303030303f7374796c653d666f722d7468652d6261646765266c6f676f3d76657263656c266c6f676f436f6c6f723d7768697465" alt="Vercel"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/30f6c0fc5ff115c4b1014d9b319e6d393bd49cb1fb83a9fc982ccb45041caaba/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4f70656e526f757465722d41492d626c756576696f6c65743f7374796c653d666f722d7468652d6261646765"&gt;&lt;img src="https://camo.githubusercontent.com/30f6c0fc5ff115c4b1014d9b319e6d393bd49cb1fb83a9fc982ccb45041caaba/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4f70656e526f757465722d41492d626c756576696f6c65743f7374796c653d666f722d7468652d6261646765" alt="OpenRouter"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Daily Automated Briefs:&lt;/strong&gt; A Vercel Cron job wakes up every morning to curate the day's top technical news.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Executive Summaries:&lt;/strong&gt; Integrates with OpenRouter (using frontier models like Grok/Llama) to distill hours of reading into 3 sharp, technical bullet points.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev Health Metrics:&lt;/strong&gt; Cross-references trending repositories with the GitHub API to display real-time stars, open issues, and last commit times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native Markdown Parsing:&lt;/strong&gt; Custom React Markdown pipeline maps AI outputs directly to native CSS variables for a seamless terminal aesthetic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge-Optimized:&lt;/strong&gt; Utilizes Next.js App Router and aggressive caching strategies for instant load times.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🏗️ System Architecture&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Cron Trigger:&lt;/strong&gt; Vercel fires a secured API route (&lt;code&gt;/api/cron/daily-brief&lt;/code&gt;) daily.&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/brighto7700/shell-signal" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Building a pixel-perfect CLI experience for mobile browsers required strict architectural choices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next.js (App Router):&lt;/strong&gt; A global &lt;code&gt;AppShell&lt;/code&gt; component maintains state across routes, so the terminal bar, system HUD, and CRT scanline overlay persist seamlessly without re-rendering when navigating between the Live Feed and the Archives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM Integration via OpenRouter:&lt;/strong&gt; The terminal generator is powered by Gemini 2.0 Flash. The core technical challenge was strict prompt engineering to force the model to return only executable bash code, with aggressive stripping of markdown backticks to prevent UI breakage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pure CSS Architecture:&lt;/strong&gt; To maintain full control over the retro aesthetic, I bypassed heavy UI libraries entirely. The Matrix-green glow, CRT scanlines, and blinking cursor animations are achieved through custom CSS variables and keyframes — keeping the DOM incredibly lightweight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Fetching:&lt;/strong&gt; A custom API route aggregates real-time stories from Hacker News, running parallel requests to the GitHub API to calculate and attach Dev Health metrics to each story before serving it to the client.&lt;/p&gt;




&lt;p&gt;Built with focus, from a hostel room in Nigeria. For every developer who just wants the signal. 🖥️&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>No Laptop? No Problem: Setting Up a Professional Kotlin &amp; Java Environment in Termux</title>
      <dc:creator>Bright Emmanuel</dc:creator>
      <pubDate>Fri, 20 Feb 2026 08:17:24 +0000</pubDate>
      <link>https://dev.to/brighto7700/no-laptop-no-problem-setting-up-a-professional-kotlin-java-environment-in-termux-4hbg</link>
      <guid>https://dev.to/brighto7700/no-laptop-no-problem-setting-up-a-professional-kotlin-java-environment-in-termux-4hbg</guid>
      <description>&lt;p&gt;A common myth in software development is that you need a $2,000 MacBook to start building professional-grade applications. While high-end hardware is nice, the most powerful tool you have is your logic—and for many of us, that logic lives on the mobile device in our pockets.&lt;br&gt;
As a student currently balancing intensive exams like JUPEB and JAMB, I’ve had to find ways to keep my development skills sharp without always being tied to a desk. In this guide, I’ll show you how to turn your Android device into a professional Kotlin and Java development environment using &lt;strong&gt;Termux&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Why Termux? (And the Play Store Trap)
&lt;/h2&gt;

&lt;p&gt;Before we start, we need to address a major "gotcha." If you search for Termux on the Google Play Store, you will find it—but do not install it from there.&lt;br&gt;
The Why: Due to changes in Android's security API levels, the Play Store version of Termux can no longer receive updates. This leads to "repository under maintenance" errors and prevents you from installing modern packages like OpenJDK 17.&lt;br&gt;
The Solution: Always download Termux from F-Droid or the official GitHub releases. This version is fully maintained and allows us to access the latest developer tools.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Preparing the Ground
&lt;/h2&gt;

&lt;p&gt;Once you have Termux installed from F-Droid, open it up. Our first task is to ensure everything is up to date and that Termux can talk to your phone's internal storage.&lt;br&gt;
Run these commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Update the package lists and upgrade existing packages
pkg update &amp;amp;&amp;amp; pkg upgrade
# Grant Termux access to your phone's folders (Downloads, Documents, etc.)
termux-setup-storage

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Installing the Engines: Java &amp;amp; Kotlin
&lt;/h2&gt;

&lt;p&gt;Java is the backbone of the Android ecosystem, and Kotlin is the modern standard. We are going to install OpenJDK 17, which is a stable, Long-Term Support (LTS) version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install OpenJDK 17
pkg install openjdk-17

# Install the Kotlin Compiler
pkg install kotlin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify the installation, check the versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;java -version
kotlinc -version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Setting Up Your Editor
&lt;/h2&gt;

&lt;p&gt;You can't write code without a good editor. While many prefer Vim, it has a steep learning curve. If you want something that feels like a desktop text editor (with mouse support and familiar shortcuts), I highly recommend &lt;strong&gt;Micro&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pkg install micro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create a new file, just type micro filename.kt. You can use Ctrl+S to save and Ctrl+Q to quit—just like on a PC.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Your First "Mobile-Made" Program
&lt;/h2&gt;

&lt;p&gt;Let’s test the environment. We will create a simple Kotlin file that displays a message.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the file: micro hello.kt&lt;/li&gt;
&lt;li&gt;Paste the following code:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun main() {
    println("Greeting from my Android device!")
    println("Hardware doesn't define a developer. Logic does.")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Save and exit (Ctrl+S, Ctrl+Q).&lt;/li&gt;
&lt;li&gt;Compile the code into a runnable JAR file:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kotlinc hello.kt -include-runtime -d hello.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Run your program:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;java -jar hello.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Pro-Tips for Mobile Devs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visual Polish (Typography):&lt;/strong&gt; For a professional experience, use a font designed for coding like JetBrains Mono or Fira Code. In the Dev.to editor, your code blocks will look great by default, but in your actual Termux terminal, a good font reduces eye strain during long sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard:&lt;/strong&gt; If you don't have a physical Bluetooth keyboard, install Hacker's Keyboard from the Play Store. It provides the Tab, Ctrl, and Esc keys that are missing from standard mobile keyboards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Battery:&lt;/strong&gt; Compiling code is CPU-intensive. Keep an eye on your battery levels, as Termux can drain it quickly during heavy builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organization:&lt;/strong&gt; Create a dedicated directory for your projects using mkdir projects to keep your home folder clean.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Coding on mobile isn't just a "backup plan." For many of us, it is the primary way we learn, iterate, and build. By setting up Kotlin and Java in Termux, you are proving that the only real barrier to entry in tech is curiosity.&lt;br&gt;
I'm currently using this exact setup to build a JUPEB/JAMB Prep Portal to help fellow students in Nigeria succeed in their exams.&lt;br&gt;
Are you building on mobile? Drop a comment below and let me know what challenges you're facing or what you're currently working on!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>android</category>
      <category>kotlin</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
