<?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>Forem: Digiwares</title>
    <description>The latest articles on Forem by Digiwares (@digitalwareshub).</description>
    <link>https://forem.com/digitalwareshub</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%2F3637065%2F71072dbc-cd17-4376-a8ca-e5e41c87ad64.png</url>
      <title>Forem: Digiwares</title>
      <link>https://forem.com/digitalwareshub</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/digitalwareshub"/>
    <language>en</language>
    <item>
      <title>How Google De-indexed My Entire Site - and What I Did to Recover</title>
      <dc:creator>Digiwares</dc:creator>
      <pubDate>Wed, 08 Apr 2026 12:39:59 +0000</pubDate>
      <link>https://forem.com/digitalwareshub/how-google-de-indexed-my-entire-site-and-what-i-did-to-recover-3jog</link>
      <guid>https://forem.com/digitalwareshub/how-google-de-indexed-my-entire-site-and-what-i-did-to-recover-3jog</guid>
      <description>&lt;p&gt;Last November, Google quietly dropped almost every page of my site from its index. I went from having 200+ pages crawled to &lt;strong&gt;1 page indexed&lt;/strong&gt; — the homepage. It took me weeks to figure out what happened, and months of work to fix it.&lt;/p&gt;

&lt;p&gt;This is the full story.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Product
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://voicetotextonline.com" rel="noopener noreferrer"&gt;VoiceToTextOnline.com&lt;/a&gt; is a browser-based voice-to-text tool. It uses the Web Speech API — no server processing, no audio stored, nothing uploaded. You open the page, click a button, and speak. The browser does all the recognition locally.&lt;/p&gt;

&lt;p&gt;It also supports file transcription (upload an MP3/WAV/MP4, get a transcript with speaker labels and SRT export) and text-to-speech via Google Cloud neural voices.&lt;/p&gt;

&lt;p&gt;I launched it in early 2025 as a solo founder. By mid-2025 it was getting 150-200 daily visitors mostly from Bing and AI referrers like ChatGPT and Doubao (ByteDance's AI assistant). One paying Pro subscriber.&lt;/p&gt;

&lt;p&gt;Then the GSC numbers started looking strange.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Happened
&lt;/h2&gt;

&lt;p&gt;I had made three mistakes simultaneously, and they compounded each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 1: www vs non-www split&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At some point my deployment config started serving the site on both &lt;code&gt;www.voicetotextonline.com&lt;/code&gt; and &lt;code&gt;voicetotextonline.com&lt;/code&gt; without a proper canonical redirect. Google was seeing duplicate versions of every page and splitting crawl budget between them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 2: Middleware blocking Googlebot&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had Next.js middleware handling auth redirects. The middleware was checking session state on every request — including requests from Googlebot. In certain conditions it was returning redirect responses to the crawler instead of the actual page content.&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;// The problematic pattern — middleware firing on all routes&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;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This was intercepting Googlebot requests&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Googlebot would hit a page, get redirected, follow the redirect, potentially get redirected again. Some pages were returning 308 chains that never resolved cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 3: Sitemap pollution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My sitemap.ts was including URLs from non-Phase-1 pages that I had intentionally set to noindex via HTTP headers. So the sitemap was advertising pages that returned &lt;code&gt;X-Robots-Tag: noindex&lt;/code&gt; — a contradiction that confused Googlebot's crawl prioritisation.&lt;/p&gt;

&lt;p&gt;The combination of all three meant Google had spent months crawling a confusing, inconsistent site — duplicate URLs, broken redirects, noindex signals fighting sitemap inclusions. Its response was to stop trusting the site almost entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result: 220 pages crawled, 1 indexed.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix (Technical)
&lt;/h2&gt;

&lt;p&gt;I combined all three fixes into a single commit in early March 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Force non-www canonical redirect&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;// middleware.ts — added before any other logic&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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;host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www.&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="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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clone&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;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&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="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Exclude Googlebot from auth middleware&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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;user-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isBot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/googlebot|bingbot|slurp|duckduckbot/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAgent&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;isBot&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Let crawlers through unconditionally&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Rebuild the sitemap to only include indexable pages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Removed all pages with noindex headers from sitemap.ts. The sitemap now only advertises pages that return 200 with no noindex signal.&lt;/p&gt;

&lt;p&gt;I submitted a GSC validation request on March 17. It failed on March 21. The crawled-but-not-indexed count was still at 91 pages.&lt;/p&gt;

&lt;p&gt;This is where the real problem became clear.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem: Thin Content at Scale
&lt;/h2&gt;

&lt;p&gt;Even with the technical issues fixed, Google wasn't indexing the language pages. And looking at them honestly, I understood why.&lt;/p&gt;

&lt;p&gt;I had 43 language-specific pages — one for each language the tool supports. Hindi voice to text, Arabic voice to text, Japanese voice to text, and so on. Each page was built from the same template:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hero with the language name&lt;/li&gt;
&lt;li&gt;4-step how-to&lt;/li&gt;
&lt;li&gt;Voice commands table&lt;/li&gt;
&lt;li&gt;"Why Choose Us" 4-card section (identical on every page)&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;CTA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The language name was swapped in. The content was largely identical. To Google's quality systems, this looked like 43 copies of the same page — thin content at scale.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;site:voicetotextonline.com&lt;/code&gt; search confirmed it. Only the homepage appeared.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Content Fix: 43 Unique Pages
&lt;/h2&gt;

&lt;p&gt;I spent several weeks rewriting all 43 language pages from scratch. The rule was simple: &lt;strong&gt;each page had to say something true and specific about that language that no other page on the site said.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This required actual research into the linguistics, diaspora communities, and cultural context of each language. A few examples of what "genuinely unique" ended up meaning:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lithuanian&lt;/strong&gt; — Lithuanian is considered the oldest surviving Indo-European language, more archaic than Latin or Sanskrit. Real Lithuanian-Sanskrit cognates exist and are fascinating: &lt;em&gt;avis&lt;/em&gt; (sheep) in Lithuanian vs &lt;em&gt;áviḥ&lt;/em&gt; in Sanskrit, &lt;em&gt;sūnus&lt;/em&gt; (son) vs &lt;em&gt;sūnúḥ&lt;/em&gt;. The page leads with this and shows a comparison table. The UK/Ireland diaspora section covers the 200,000+ Lithuanians who moved after EU accession in 2004 and face keyboard incompatibility with the 9 special Lithuanian characters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slovenian&lt;/strong&gt; — One of the only living languages with a grammatical dual number — a distinct form for exactly two of something. The three-card visual showing ednina (singular) / dvojina (dual) / množina (plural) with real examples is content no competitor page has.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Catalan&lt;/strong&gt; — 10 million speakers but no EU official status, despite having more native speakers than Irish, Maltese, and Luxembourgish combined. The comparison table makes this concrete. The &lt;code&gt;l·l&lt;/code&gt; character (ela geminada) exists only in Catalan and is impossible to type on any standard keyboard — the page explains this and positions voice typing as the solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bulgarian&lt;/strong&gt; — The Cyrillic alphabet was created in Bulgaria in the 9th century. Every other Cyrillic language page can claim the script as their writing system. Only Bulgarian can claim they gave it to the world. The three-card section (9th century / 250M+ users / 50+ languages) is genuine and searchable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Afrikaans&lt;/strong&gt; — The youngest natural language in the world (standardised 1925) and one of the most accurately recognised by Web Speech API, because it has no special characters and writes almost exactly as it sounds. The comparison table shows why Afrikaans outperforms German, Danish, and Dutch for voice recognition accuracy.&lt;/p&gt;

&lt;p&gt;After 43 rewrites, every page has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A unique lead angle based on genuine linguistics&lt;/li&gt;
&lt;li&gt;A named culturally-specific section (diaspora keyboards, minority status, historical alphabet events)&lt;/li&gt;
&lt;li&gt;A sample output block showing real text in the target script&lt;/li&gt;
&lt;li&gt;5 language-specific FAQs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Internal Linking Gap
&lt;/h2&gt;

&lt;p&gt;While doing this I discovered another issue: every language page's breadcrumb pointed to &lt;code&gt;/#languages&lt;/code&gt; — an anchor that didn't exist on the homepage. 43 pages with broken breadcrumb links.&lt;/p&gt;

&lt;p&gt;The fix was building a proper &lt;code&gt;/languages&lt;/code&gt; index page linking to all 43 pages, grouped by region, with flag, native script, and speaker count for each. This also gave Google a clear crawl path: homepage → /languages → all 43 language pages.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current Status
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;10 rewritten language pages submitted to GSC via URL Inspection&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;/languages&lt;/code&gt; index page deployed and submitted&lt;/li&gt;
&lt;li&gt;GSC still shows 1 indexed page (the homepage)&lt;/li&gt;
&lt;li&gt;91 pages in "Crawled - currently not indexed" with failed validation&lt;/li&gt;
&lt;li&gt;124 pages "Discovered - currently not indexed" with passed validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 124 "Discovered" pages are the hopeful signal — Google has them queued. The 91 failed pages are the harder problem: Google formed its quality judgment when the content was thin, and that judgment is sticky even after the content improves.&lt;/p&gt;

&lt;p&gt;The honest timeline for recovery is 3-6 months from the content fix. The technical fixes were necessary but not sufficient. The content rewrites were necessary but not sufficient. What's likely needed next is external backlinks — at this domain trust level, Google needs signals from other sites before it will fully re-evaluate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. www/non-www is not a minor detail.&lt;/strong&gt; It's a canonical URL decision that affects every page on your site. Pick one and enforce it with a 301 at the infrastructure level, not just in meta tags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Middleware that touches all routes will touch Googlebot.&lt;/strong&gt; Any redirect logic in Next.js middleware needs a bot exemption or it will silently confuse crawlers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Sitemaps and robots signals must be consistent.&lt;/strong&gt; If a page is in your sitemap, it should return 200 with no noindex. If it's noindexed, remove it from the sitemap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Template pages at scale look like duplicate content.&lt;/strong&gt; 43 pages with the same structure and swapped keywords is not 43 unique pages. Each page needs to say something the others don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Google's quality judgment is sticky.&lt;/strong&gt; Fixing the content doesn't immediately fix the index. Google cached its assessment when the pages were bad. Re-earning trust takes time and likely external validation.&lt;/p&gt;




&lt;p&gt;If you're building a multi-language tool and want to avoid this — hope the technical details here save you some pain.&lt;/p&gt;

&lt;p&gt;The tool is at &lt;a href="https://voicetotextonline.com" rel="noopener noreferrer"&gt;voicetotextonline.com&lt;/a&gt; if you want to see what the rewritten pages look like. The Lithuanian and Slovenian pages are probably the most interesting from a linguistics angle.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Solo founder, building in public from Bangkok. This is month 13 with zero revenue. The goal is $5K MRR. Current status: 1 paying subscriber.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>nextjs</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Built a Free Voice-to-Text Tool That Supports 55+ Languages published: true</title>
      <dc:creator>Digiwares</dc:creator>
      <pubDate>Tue, 03 Feb 2026 03:54:09 +0000</pubDate>
      <link>https://forem.com/digitalwareshub/i-built-a-free-voice-to-text-tool-that-supports-55-languagespublished-true-25ma</link>
      <guid>https://forem.com/digitalwareshub/i-built-a-free-voice-to-text-tool-that-supports-55-languagespublished-true-25ma</guid>
      <description>&lt;p&gt;We developed VoiceToTextOnline.com in a period of 3 months and now on Month 4 as I type this post. On month 4, we are getting 200 visitors a day and bounce rate of 50 to 52%.&lt;/p&gt;

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

&lt;p&gt;It was a lot of effort working on the SEO to drive traffic. Now my No.1 traffic source is Bing and we rank No.3 on there but ranking on Google is still a far away story! I even setup 2 satellite websites to drive traffic last week- speechtotext.online and texttospeech.site. They have been able to get around 10 to 20 unique visitors a day. In the last 3 months, we have a solid traffic base. The only worry point is we are still not even able to make it to the second or third page of Google SERP.&lt;/p&gt;

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

&lt;p&gt;Based on user request we also setup a new tool on the website - text to speech / TTS though our site says voice to text online dot com.&lt;/p&gt;

&lt;p&gt;I want to ask the fellow developers - what strategy do you use to rank on Google?&lt;/p&gt;

&lt;p&gt;Introducing Voice to Text Online - a powerful speech recognition tool that works right in your browser.&lt;/p&gt;

&lt;p&gt;🌍 55+ LANGUAGES SUPPORTED&lt;br&gt;
Hindi • Spanish • Arabic • French • German • Italian • Portuguese • Chinese • Japanese • Korean • Russian • Turkish • Tamil • Telugu • Bengali • and 40+ more!&lt;/p&gt;

&lt;p&gt;✨ WHY PROFESSIONALS LOVE IT:&lt;/p&gt;

&lt;p&gt;→ No account needed — just open and start talking&lt;br&gt;
→ Real-time transcription — see your words appear instantly&lt;br&gt;
→ Works on any device — phone, tablet, or computer&lt;br&gt;
→ No downloads required — 100% browser-based&lt;br&gt;
→ Privacy-first — your voice data stays on your device&lt;/p&gt;

&lt;p&gt;💼 PERFECT FOR:&lt;br&gt;
📝 Taking meeting notes&lt;br&gt;
✍️ Drafting emails and documents&lt;br&gt;
🎙️ Transcribing interviews&lt;br&gt;
📚 Creating content faster&lt;br&gt;
🌐 Multilingual teams&lt;/p&gt;

&lt;p&gt;The best part? It's completely free. No hidden fees, no premium walls for basic features.&lt;/p&gt;

&lt;p&gt;👉 Try it now: voicetotextonline.com&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Built a Group Chat That Draws Itself Into a Graph - Here's How</title>
      <dc:creator>Digiwares</dc:creator>
      <pubDate>Mon, 26 Jan 2026 09:38:35 +0000</pubDate>
      <link>https://forem.com/digitalwareshub/i-built-a-group-chat-that-draws-itself-into-a-graph-heres-how-2oec</link>
      <guid>https://forem.com/digitalwareshub/i-built-a-group-chat-that-draws-itself-into-a-graph-heres-how-2oec</guid>
      <description>&lt;h1&gt;
  
  
  I Built a Group Chat That Draws Itself Into a Graph - Here's How
&lt;/h1&gt;

&lt;p&gt;Ever been in a group chat where great ideas just... disappear? You're brainstorming with your team, someone drops a brilliant insight, and 30 messages later it's buried forever.&lt;/p&gt;

&lt;p&gt;I got tired of scrolling through walls of text trying to find "that thing someone said earlier," so I built &lt;a href="https://qonvo.xyz" rel="noopener noreferrer"&gt;Qonvo&lt;/a&gt; — a group chat that visualizes conversations as an interactive graph in real-time.&lt;/p&gt;

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

&lt;p&gt;Traditional chat is linear. But conversations aren't.&lt;/p&gt;

&lt;p&gt;We jump between topics, reference earlier points, and connect ideas across threads. Forcing all of this into a single scrolling column loses context and makes it hard to see how ideas relate.&lt;/p&gt;

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

&lt;p&gt;What if every message was a node, every reply was a connection, and you could see the entire conversation at once?&lt;/p&gt;

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

&lt;p&gt;That's Qonvo. As you chat, a force-directed graph builds in real-time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Messages → Nodes&lt;/strong&gt; (color-coded by sender)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replies → Solid edges&lt;/strong&gt; (direct connections)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#Tags → Dashed edges&lt;/strong&gt; (link related ideas across threads)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click any node to focus it and see its connections. The graph updates live as people chat.&lt;/p&gt;

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

&lt;p&gt;Built this as a solo project in about 2 weeks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Vanilla JS + D3.js (no React, no build step)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Node.js + Express + Socket.io&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence:&lt;/strong&gt; Redis (Upstash) with 3-hour TTL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting:&lt;/strong&gt; Railway&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Vanilla JS?
&lt;/h3&gt;

&lt;p&gt;Honestly? Speed. I wanted to ship fast without wrestling with bundlers. The entire frontend is a single 1,800-line HTML file. Is it perfect? No. Does it work? Yes.&lt;/p&gt;

&lt;p&gt;D3's force simulation handles the graph physics. Socket.io keeps everyone in sync. That's really it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core Graph Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Every message becomes a node&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;nanoid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sender&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;text&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="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;extractTags&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="c1"&gt;// #hashtags&lt;/span&gt;
  &lt;span class="na"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Replies create edges&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;replyTo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;edges&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="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;replyTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reply&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="c1"&gt;// Tags connect related messages&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&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;lastWithTag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findLastMessageWithTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&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;lastWithTag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;edges&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="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lastWithTag&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;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tag&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;tag&lt;/span&gt; 
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;1. Ephemeral is a feature, not a bug&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rooms auto-delete after 3 hours. No accounts, no sign-ups, no data hoarding. Users actually love this — it makes the tool feel safe for quick, throwaway conversations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The graph needs to be optional&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On mobile, showing chat + graph doesn't work. So there's a toggle to open the graph as an overlay. Desktop shows both side-by-side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Force simulations are tricky&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Getting nodes to not overlap while keeping connected nodes close took a lot of tweaking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;simulation&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;charge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forceManyBody&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;collision&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forceCollide&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forceLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://qonvo.xyz" rel="noopener noreferrer"&gt;qonvo.xyz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a demo room with sample data, or create your own room and share the link.&lt;/p&gt;

&lt;p&gt;No sign-up. No install. Just click and chat.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Paid room extensions&lt;/strong&gt; — Keep rooms alive for 7/30 days instead of 3 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export to Markdown&lt;/strong&gt; — Turn conversations into documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PWA support&lt;/strong&gt; — Installable on mobile&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Feedback Welcome
&lt;/h2&gt;

&lt;p&gt;This is my first real "ship it and see" project. Would love to hear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the visualization actually useful, or just a gimmick?&lt;/li&gt;
&lt;li&gt;What would make you use this over a regular group chat?&lt;/li&gt;
&lt;li&gt;Any UX issues I should fix?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop a comment or try the &lt;a href="https://qonvo.xyz" rel="noopener noreferrer"&gt;demo&lt;/a&gt; and let me know what breaks.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building in public as a solo founder. Follow along: &lt;a href="https://twitter.com/digi_wares" rel="noopener noreferrer"&gt;@digi_wares&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Built a Free Article-to-Audio Converter!</title>
      <dc:creator>Digiwares</dc:creator>
      <pubDate>Fri, 23 Jan 2026 05:45:01 +0000</pubDate>
      <link>https://forem.com/digitalwareshub/i-built-a-free-article-to-audio-converter-50ad</link>
      <guid>https://forem.com/digitalwareshub/i-built-a-free-article-to-audio-converter-50ad</guid>
      <description>&lt;h1&gt;
  
  
  I Built a Free Article-to-Audio Converter in a Weekend
&lt;/h1&gt;

&lt;p&gt;My "read later" list was out of control. Hundreds of articles I'd never get to. So I built &lt;a href="https://sornic.com" rel="noopener noreferrer"&gt;Sornic&lt;/a&gt; — paste any article URL, get audio in seconds.&lt;/p&gt;

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

&lt;p&gt;I wanted to catch up on articles while commuting, cooking, or working out. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pocket/Instapaper just pile up unread&lt;/li&gt;
&lt;li&gt;Most TTS apps sound robotic&lt;/li&gt;
&lt;li&gt;Browser extensions are clunky&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something dead simple: URL in, audio out.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 14&lt;/strong&gt; (App Router)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI TTS API&lt;/strong&gt; (natural voices)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; (hosting + serverless functions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upstash Redis&lt;/strong&gt; (rate limiting)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; (styling)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Article Extraction
&lt;/h3&gt;

&lt;p&gt;When you paste a URL, the server fetches the page and extracts the article content using Mozilla's Readability library (same one Firefox uses for Reader View).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dom&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;JSDOM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&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;Readability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&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;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reader&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For JS-heavy sites that don't work with simple fetch, I fall back to Firecrawl.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Content Cleanup
&lt;/h3&gt;

&lt;p&gt;Raw extracted text often includes navigation, ads, "Subscribe now!" prompts. I use Claude Haiku to clean it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-haiku-4-20250514&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Clean this article for text-to-speech.
              Remove nav, ads, CTAs. Keep only the article body.
              &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rawText&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;h3&gt;
  
  
  3. Text-to-Speech
&lt;/h3&gt;

&lt;p&gt;OpenAI's TTS API has a 4096 character limit, so I chunk long articles:&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;splitTextIntoChunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Break at sentence boundaries when possible&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sentenceMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.*&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;.!?&lt;/span&gt;&lt;span class="se"&gt;]\s&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then generate audio for each chunk and concatenate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;chunks&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;mp3Response&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;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speech&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tts-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nova&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;audioBuffers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mp3Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&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;
  
  
  4. Rate Limiting
&lt;/h3&gt;

&lt;p&gt;Free tier = 5 articles/day per IP. Using Upstash Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ratelimit&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;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEnv&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fixedWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;24h&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;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Vercel Timeouts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Default is 10 seconds. Long articles can take 30-60 seconds to process. Fixed with:&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;"functions"&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;"app/api/**/*.ts"&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;"maxDuration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;p&gt;&lt;strong&gt;2. ESM/CommonJS Conflicts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;jsdom v27 broke on Vercel due to ESM issues. Downgraded to v24:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Sites Blocking Scraping&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some sites block server-side requests. Firecrawl handles these as a fallback — it uses headless browsers and handles anti-bot measures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Breakdown
&lt;/h2&gt;

&lt;p&gt;Per article (~2000 words):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI TTS: ~$0.03&lt;/li&gt;
&lt;li&gt;Claude Haiku cleanup: ~$0.001&lt;/li&gt;
&lt;li&gt;Vercel/Upstash: Free tier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 5 free articles/user/day, costs stay manageable with the rate limit.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Download as MP3&lt;/li&gt;
&lt;li&gt;Browser extension&lt;/li&gt;
&lt;li&gt;Playlist/queue feature&lt;/li&gt;
&lt;li&gt;Premium tier with more articles&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;a href="https://sornic.com" rel="noopener noreferrer"&gt;sornic.com&lt;/a&gt; — no signup required.&lt;/p&gt;

&lt;p&gt;Drop a URL, pick a voice, hit play. Would love feedback on what features would make it more useful.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; webdev, nextjs, openai, javascript&lt;/p&gt;

</description>
      <category>podcast</category>
      <category>webdev</category>
      <category>nextjs</category>
      <category>openai</category>
    </item>
    <item>
      <title>What Will Actually Work in SaaS in 2026 (And What Won't)</title>
      <dc:creator>Digiwares</dc:creator>
      <pubDate>Tue, 20 Jan 2026 09:40:22 +0000</pubDate>
      <link>https://forem.com/digitalwareshub/what-will-actually-work-in-saas-in-2026-and-what-wont-2c7l</link>
      <guid>https://forem.com/digitalwareshub/what-will-actually-work-in-saas-in-2026-and-what-wont-2c7l</guid>
      <description>&lt;p&gt;I've been thinking a lot about this lately.&lt;/p&gt;

&lt;p&gt;Mostly because of how many projects I've personally built that were GOOD, but still didn't really go anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;70% failed. 30% are in high stormy seas.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They were not broken.&lt;br&gt;
They were not useless.&lt;br&gt;
They were just... optional.&lt;/p&gt;

&lt;p&gt;So instead of guessing trends, I started looking at why certain tools quietly disappear and why others stick, even if they're boring.&lt;/p&gt;

&lt;p&gt;Here's where I've landed for 2026.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is opinionated and based on my experience. Happy to be wrong. But this is how I'm planning my own builds now.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Think Will Work in 2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Tools That Automate Decisions, Not Just Tasks
&lt;/h3&gt;

&lt;p&gt;Automation today is still very "if this, then that". So many ifs and buts. That's fine, but still shallow.&lt;/p&gt;

&lt;p&gt;The tools that matter are the ones that decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When something matters&lt;/li&gt;
&lt;li&gt;When to escalate&lt;/li&gt;
&lt;li&gt;When to block&lt;/li&gt;
&lt;li&gt;When to trigger the next step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If your software reduces the number of decisions a human has to make, it becomes hard to remove.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If it only saves a few clicks, it's optional.&lt;/p&gt;

&lt;p&gt;That's the math.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Software That Lives Inside Real Workflows
&lt;/h3&gt;

&lt;p&gt;Standalone tools are dying quietly.&lt;/p&gt;

&lt;p&gt;The stuff that sticks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plugs into existing systems&lt;/li&gt;
&lt;li&gt;Reacts automatically&lt;/li&gt;
&lt;li&gt;Runs in the background&lt;/li&gt;
&lt;li&gt;Doesn't require people to remember to open it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If your product depends on someone logging in regularly to "check" something, you're fighting human nature.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Boring, But Regulated and High-Stakes Tools
&lt;/h3&gt;

&lt;p&gt;This took me a while to accept.&lt;/p&gt;

&lt;p&gt;The exciting ideas are crowded. The boring ones are ignored.&lt;/p&gt;

&lt;p&gt;Compliance, finance, ops, contracts, security, governance... these are annoying problems, but they don't go away.&lt;/p&gt;

&lt;p&gt;Even though I kept realizing this, I ignored it because I'm not a subject matter expert in these areas.&lt;/p&gt;

&lt;p&gt;But people happily pay for software that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces risk&lt;/li&gt;
&lt;li&gt;Avoids mistakes&lt;/li&gt;
&lt;li&gt;Prevents "oh shit" moments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if the UI is dull.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Systems That Get Better The Longer They Run
&lt;/h3&gt;

&lt;p&gt;Most tools reset every session.&lt;/p&gt;

&lt;p&gt;The ones that win will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remember past decisions&lt;/li&gt;
&lt;li&gt;Learn from overrides&lt;/li&gt;
&lt;li&gt;Adapt based on outcomes&lt;/li&gt;
&lt;li&gt;Improve without constant rebuilding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A perfect example: LLMs like ChatGPT and Claude become better every day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Software without memory doesn't compound.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. AI as Infrastructure, Not the Headline
&lt;/h3&gt;

&lt;p&gt;AI will be everywhere by default.&lt;/p&gt;

&lt;p&gt;The tools that survive won't market themselves as "AI-powered". They'll just quietly use AI where rules fall short.&lt;/p&gt;

&lt;p&gt;Here's the test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If removing AI breaks your product completely, your tool is a dependency on itself and its existence&lt;/li&gt;
&lt;li&gt;If removing AI makes it worse but still functional, that's healthier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The moment your software takes responsibility, it becomes trusted.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Think Won't Work (Or Will Be Very Hard)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Standalone AI Tools That Only "Generate Outputs"
&lt;/h3&gt;

&lt;p&gt;Text in → text out.&lt;/p&gt;

&lt;p&gt;Even if it's good. Even if it's fast. Even if it's cheap.&lt;/p&gt;

&lt;p&gt;If it doesn't connect to anything else or drive action, it's a one-time curiosity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;People try it once and move on.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Insight-Only Dashboards
&lt;/h3&gt;

&lt;p&gt;I've built these. They look great.&lt;/p&gt;

&lt;p&gt;But "here's what's happening" without:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Here's what we're doing"&lt;/li&gt;
&lt;li&gt;"Here's what happens if you don't act"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...doesn't change behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Charts without consequences fade into the background. No one needs them. They get trashed.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Tools That Require Constant User Attention
&lt;/h3&gt;

&lt;p&gt;Anything that depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daily check-ins&lt;/li&gt;
&lt;li&gt;Manual reviews&lt;/li&gt;
&lt;li&gt;Remembering to come back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...is fragile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;People don't want more software to think about. They want fewer things to worry about.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Generic Tools With No Domain Depth
&lt;/h3&gt;

&lt;p&gt;General-purpose tools that are easy to copy and hard to defend.&lt;/p&gt;

&lt;p&gt;The deeper the domain, the harder it is to replicate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Works for everyone" usually means "perfect for no one."&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Products That Exist Only Inside Their Own UI
&lt;/h3&gt;

&lt;p&gt;If your product can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trigger workflows&lt;/li&gt;
&lt;li&gt;Integrate cleanly&lt;/li&gt;
&lt;li&gt;Influence other systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...it stays isolated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isolated tools get replaced. The end.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Thumb Rule Before Building Your 2026 SaaS
&lt;/h2&gt;

&lt;p&gt;The tools that work in 2026 will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automate decisions, not tasks&lt;/li&gt;
&lt;li&gt;Operate across systems&lt;/li&gt;
&lt;li&gt;Reduce human babysitting&lt;/li&gt;
&lt;li&gt;Enforce rules&lt;/li&gt;
&lt;li&gt;Earn trust over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tools that don't will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Look impressive&lt;/li&gt;
&lt;li&gt;Feel clever&lt;/li&gt;
&lt;li&gt;Get tried once&lt;/li&gt;
&lt;li&gt;And quietly get trashed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Build one tool that fewer people understand at first, but no one wants to remove once it's up and running.&lt;/p&gt;

&lt;p&gt;Stop building optional software.&lt;/p&gt;

&lt;p&gt;Build essential infrastructure.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;P.S. - I'm not saying don't build AI tools. I'm saying don't build tools where AI is the only thing holding it together. Build tools where AI makes an already useful thing even better. That's the difference between a demo and a business.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>saas</category>
      <category>buildinpublic</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A small web app where people quietly appear on a world map</title>
      <dc:creator>Digiwares</dc:creator>
      <pubDate>Sat, 03 Jan 2026 06:11:42 +0000</pubDate>
      <link>https://forem.com/digitalwareshub/a-small-web-app-where-people-quietly-appear-on-a-world-map-1fa</link>
      <guid>https://forem.com/digitalwareshub/a-small-web-app-where-people-quietly-appear-on-a-world-map-1fa</guid>
      <description>&lt;p&gt;I've moved around a lot. Finding information about a new city is easy- Google has everything. But finding &lt;em&gt;people&lt;/em&gt;? That's weirdly hard.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://mapmates.io" rel="noopener noreferrer"&gt;MapMates&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;A map. You drop a pin. Others appear. That's it.&lt;/p&gt;

&lt;p&gt;No signup walls. No algorithmic feed. No tracking. Just people, quietly appearing on a world map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who it's for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Digital nomads wondering who else is in Bali&lt;/li&gt;
&lt;li&gt;Expats in Bangkok looking for other remote workers&lt;/li&gt;
&lt;li&gt;Freelancers in Lisbon hoping to find a co-working buddy&lt;/li&gt;
&lt;li&gt;Retirees in Portugal curious about their community&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 14&lt;/strong&gt; (App Router)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; (Postgres + Anonymous Auth)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaflet&lt;/strong&gt; (maps)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upstash Redis&lt;/strong&gt; (rate limiting)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; (styling)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; (hosting)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How anonymous auth works
&lt;/h2&gt;

&lt;p&gt;Most apps force you to sign up before doing anything. I wanted the opposite — drop a pin in 30 seconds, no email required.&lt;/p&gt;

&lt;p&gt;Supabase has a neat feature: anonymous authentication. Users get a real auth session without providing credentials. If they clear their browser, they lose access — but for a casual "pin yourself on a map" use case, that's fine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&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;signInAnonymously&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. User is authenticated. No friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limiting
&lt;/h2&gt;

&lt;p&gt;To prevent spam, I added rate limits using Upstash Redis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 pin per user (editable)&lt;/li&gt;
&lt;li&gt;3 connection requests per day
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ratelimit&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;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEnv&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slidingWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 d&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;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;City pages (&lt;code&gt;mapmates.io/bangkok&lt;/code&gt;, &lt;code&gt;mapmates.io/lisbon&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Email notifications when someone wants to connect&lt;/li&gt;
&lt;li&gt;Let anyone create their own community map&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://mapmates.io" rel="noopener noreferrer"&gt;mapmates.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Zoom in. See who's around. Maybe drop a pin.&lt;/p&gt;




&lt;p&gt;Would love feedback from the dev community. What would you add?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>community</category>
      <category>networking</category>
    </item>
    <item>
      <title>Building a Browser-Based Voice-to-Text App with the Web Speech API</title>
      <dc:creator>Digiwares</dc:creator>
      <pubDate>Fri, 12 Dec 2025 17:49:43 +0000</pubDate>
      <link>https://forem.com/digitalwareshub/building-a-browser-based-voice-to-text-app-with-the-web-speech-api-2ni4</link>
      <guid>https://forem.com/digitalwareshub/building-a-browser-based-voice-to-text-app-with-the-web-speech-api-2ni4</guid>
      <description>&lt;h1&gt;
  
  
  Building a Browser-Based Voice-to-Text App with the Web Speech API
&lt;/h1&gt;

&lt;p&gt;I recently built a voice-to-text tool that works entirely in the browser — no backend required for the core functionality. Here's what I learned about the Web Speech API and its quirks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Browser-Based?
&lt;/h2&gt;

&lt;p&gt;Privacy is the main sell. Audio never leaves the user's device. No uploads, no storage, no GDPR headaches. For a simple transcription tool, this is a huge advantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Web Speech API Basics
&lt;/h2&gt;

&lt;p&gt;The API is surprisingly simple:&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;recognition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SpeechRecognition&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webkitSpeechRecognition&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;

&lt;span class="nx"&gt;recognition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;continuous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;recognition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interimResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;recognition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;recognition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onresult&lt;/span&gt; &lt;span class="o"&gt;=&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;result&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;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;recognition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You now have live speech-to-text.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gotchas Nobody Warns You About
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Browser support is inconsistent
&lt;/h3&gt;

&lt;p&gt;Chrome uses Google's servers (ironically, not fully local). Safari uses on-device processing. Firefox support is limited. Always check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SpeechRecognition&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webkitSpeechRecognition&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Show fallback UI&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. It stops listening randomly
&lt;/h3&gt;

&lt;p&gt;The API has a habit of stopping after silence. You need to restart it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;recognition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shouldKeepListening&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;recognition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Punctuation doesn't exist
&lt;/h3&gt;

&lt;p&gt;The API returns raw words with no periods, commas, or capitalization. You'll need to handle this yourself:&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;function&lt;/span&gt; &lt;span class="nf"&gt;addAutoPunctuation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Add period after pause patterns&lt;/span&gt;
  &lt;span class="c1"&gt;// Capitalize after periods&lt;/span&gt;
  &lt;span class="c1"&gt;// Handle common patterns like "question mark" → "?"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Language switching is manual
&lt;/h3&gt;

&lt;p&gt;You need to build your own language selector and set &lt;code&gt;recognition.lang&lt;/code&gt; accordingly. The API supports 100+ languages but won't auto-detect.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to NOT Use Web Speech API
&lt;/h2&gt;

&lt;p&gt;For anything beyond basic dictation, you'll hit walls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audio file transcription&lt;/strong&gt; — API only does live mic input&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speaker identification&lt;/strong&gt; — Not supported&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timestamps&lt;/strong&gt; — Not provided&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accuracy requirements&lt;/strong&gt; — Enterprise use cases need Whisper, AssemblyAI, or Deepgram&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ended up building a hybrid: free tier uses Web Speech API for live dictation, Pro tier uses Whisper for file uploads and higher accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native Language SEO Bonus
&lt;/h2&gt;

&lt;p&gt;One unexpected win: I built language-specific pages with native script UI. The Hindi page is actually in Hindi (हिंदी में वॉइस टू टेक्स्ट), not just "Hindi Voice to Text" in English.&lt;/p&gt;

&lt;p&gt;Result: Started ranking for native-language searches with way less competition than English keywords.&lt;/p&gt;

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

&lt;p&gt;I built this into &lt;a href="https://voicetotextonline.com" rel="noopener noreferrer"&gt;voicetotextonline.com&lt;/a&gt; — free to use, no signup for basic transcription.&lt;/p&gt;

&lt;p&gt;If you're building something similar, happy to answer questions in the comments.&lt;/p&gt;

</description>
      <category>webapi</category>
      <category>javascript</category>
      <category>productivity</category>
      <category>speechtotext</category>
    </item>
    <item>
      <title>I built an AI radio that reads the internet to you</title>
      <dc:creator>Digiwares</dc:creator>
      <pubDate>Sun, 30 Nov 2025 07:22:46 +0000</pubDate>
      <link>https://forem.com/digitalwareshub/i-built-an-ai-radio-that-reads-the-internet-to-you-23fd</link>
      <guid>https://forem.com/digitalwareshub/i-built-an-ai-radio-that-reads-the-internet-to-you-23fd</guid>
      <description>&lt;p&gt;Hey folks 👋&lt;/p&gt;

&lt;p&gt;I got tired of endlessly scrolling Hacker News during my commute — I love the discussions, but never have enough time (or attention) to read everything.&lt;/p&gt;

&lt;p&gt;So I built something small but useful: &lt;strong&gt;Tera.fm&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://www.tera.fm" rel="noopener noreferrer"&gt;https://www.tera.fm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It fetches the latest top stories from places like &lt;strong&gt;Hacker News&lt;/strong&gt;, summarizes them using AI, extracts key viewpoints from the comment section, and &lt;em&gt;reads everything aloud&lt;/em&gt; in a natural voice.&lt;/p&gt;

&lt;p&gt;Basically:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Hacker News → summarized → spoken like a radio station.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why I built it&lt;br&gt;
I take a lot of crowded trains and often cook while listening to podcasts. I always wished there was a simple “internet radio” that reads technical news to me without needing to scroll or tap.&lt;/p&gt;

&lt;p&gt;No login. No tracking. No feeds to manage.&lt;br&gt;&lt;br&gt;
Just hit play and listen — while commuting, cooking, or at the gym.&lt;/p&gt;

&lt;p&gt;What’s inside right now&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HN Top
&lt;/li&gt;
&lt;li&gt;HN New
&lt;/li&gt;
&lt;li&gt;HN Ask
&lt;/li&gt;
&lt;li&gt;HN Show
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What’s coming next&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reddit (World News, Science, TIL)
&lt;/li&gt;
&lt;li&gt;Product Hunt
&lt;/li&gt;
&lt;li&gt;Dev.to trending
&lt;/li&gt;
&lt;li&gt;GitHub Trending
&lt;/li&gt;
&lt;li&gt;Custom channels
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stack&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js + Vercel
&lt;/li&gt;
&lt;li&gt;OpenAI Realtime TTS (Alloy voice)
&lt;/li&gt;
&lt;li&gt;Hacker News API
&lt;/li&gt;
&lt;li&gt;Clean, minimal UI
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback welcome 🙏&lt;br&gt;
This is still early. Would love thoughts on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the voice quality
&lt;/li&gt;
&lt;li&gt;summaries
&lt;/li&gt;
&lt;li&gt;channel ideas
&lt;/li&gt;
&lt;li&gt;UX improvements
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you listen to technical content or browse HN regularly, I’d love your feedback.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;br&gt;&lt;br&gt;
Digiwares&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>ai</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
