<?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: Nuco Z</title>
    <description>The latest articles on DEV Community by Nuco Z (@nuco_z_270906fb0e460592db).</description>
    <link>https://dev.to/nuco_z_270906fb0e460592db</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3629891%2Ff0b9fcae-351e-4316-b5cf-1fdb20a0c398.png</url>
      <title>DEV Community: Nuco Z</title>
      <link>https://dev.to/nuco_z_270906fb0e460592db</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nuco_z_270906fb0e460592db"/>
    <language>en</language>
    <item>
      <title>How I Remove People From Photos Without Ruining the Background</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Thu, 27 Aug 2026 08:44:36 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/how-i-remove-people-from-photos-without-ruining-the-background-4kfc</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/how-i-remove-people-from-photos-without-ruining-the-background-4kfc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63eqedvpjp34jgt752j8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63eqedvpjp34jgt752j8.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Have you ever taken a photo that was almost perfect, only to notice someone you didn't want in the background?&lt;/p&gt;

&lt;p&gt;A stranger walking through the shot.&lt;/p&gt;

&lt;p&gt;Someone standing behind you.&lt;/p&gt;

&lt;p&gt;A person appearing in the corner at exactly the wrong moment.&lt;/p&gt;

&lt;p&gt;The frustrating part is that the photo itself is usually fine. You just want to remove one thing.&lt;/p&gt;

&lt;p&gt;But removing a person is only half of the problem.&lt;/p&gt;

&lt;p&gt;The harder part is making the background look natural afterward.&lt;/p&gt;

&lt;p&gt;I’ve been working on a simple way to handle this using AI, and along the way I found a few things that make a surprisingly big difference.&lt;/p&gt;

&lt;p&gt;Start with the original photo&lt;/p&gt;

&lt;p&gt;The first thing I recommend is using the highest-quality version of the original image you have.&lt;/p&gt;

&lt;p&gt;It might sound obvious, but the surrounding image gives an AI removal tool important information about what should replace the person.&lt;/p&gt;

&lt;p&gt;A person standing in front of a plain wall is relatively straightforward.&lt;/p&gt;

&lt;p&gt;A person standing in front of a complicated background is a completely different problem.&lt;/p&gt;

&lt;p&gt;Before editing, I usually keep the original image untouched and work on a separate copy.&lt;/p&gt;

&lt;p&gt;Select the entire person&lt;/p&gt;

&lt;p&gt;One of the easiest mistakes is selecting only the obvious part of the person.&lt;/p&gt;

&lt;p&gt;If you want to remove someone, try to include their entire body.&lt;/p&gt;

&lt;p&gt;That means their head, hair, body, hands, legs, shoes, bags, and loose clothing.&lt;/p&gt;

&lt;p&gt;Otherwise, you may end up with something strange like a hand or shoe remaining in the final image.&lt;/p&gt;

&lt;p&gt;At the same time, don't make the selection unnecessarily huge.&lt;/p&gt;

&lt;p&gt;If there is another person standing nearby, selecting too much can affect parts of the image you actually wanted to keep.&lt;/p&gt;

&lt;p&gt;Give the selection enough room, but stay aware of what's around it.&lt;/p&gt;

&lt;p&gt;Don't forget the shadow&lt;/p&gt;

&lt;p&gt;This is probably the detail people notice only after they have already removed the person.&lt;/p&gt;

&lt;p&gt;Imagine removing someone from a beach photo.&lt;/p&gt;

&lt;p&gt;The person disappears.&lt;/p&gt;

&lt;p&gt;But their shadow is still sitting on the sand.&lt;/p&gt;

&lt;p&gt;Something immediately feels wrong.&lt;/p&gt;

&lt;p&gt;The same thing can happen with shadows on roads, floors, walls, or other surfaces.&lt;/p&gt;

&lt;p&gt;So before removing someone, look around their feet and underneath their body.&lt;/p&gt;

&lt;p&gt;Ask yourself:&lt;/p&gt;

&lt;p&gt;What else in this photo exists because this person is here?&lt;/p&gt;

&lt;p&gt;Sometimes removing the shadow or reflection makes as much difference as removing the person itself.&lt;/p&gt;

&lt;p&gt;Be careful when people are close together&lt;/p&gt;

&lt;p&gt;Removing one person from a crowded photo is much harder than removing someone standing alone.&lt;/p&gt;

&lt;p&gt;If two people are next to each other, a large brush can easily include part of the person you want to keep.&lt;/p&gt;

&lt;p&gt;The same problem can happen around faces, hands, bags, signs, railings, windows, and other objects.&lt;/p&gt;

&lt;p&gt;For these situations, I prefer using a more controlled selection.&lt;/p&gt;

&lt;p&gt;The goal isn't to make the biggest possible selection.&lt;/p&gt;

&lt;p&gt;The goal is to tell the AI exactly what needs to change.&lt;/p&gt;

&lt;p&gt;Look at the background, not just the person&lt;/p&gt;

&lt;p&gt;After the removal is finished, don't immediately decide that the result is good just because the person is gone.&lt;/p&gt;

&lt;p&gt;Look at the background.&lt;/p&gt;

&lt;p&gt;Does the ground still look natural?&lt;/p&gt;

&lt;p&gt;Did a straight line suddenly move?&lt;/p&gt;

&lt;p&gt;Is a texture repeated?&lt;/p&gt;

&lt;p&gt;Did something near the person change unexpectedly?&lt;/p&gt;

&lt;p&gt;Does the surrounding area still make sense?&lt;/p&gt;

&lt;p&gt;I like to check the image at two different levels.&lt;/p&gt;

&lt;p&gt;First, look at the entire photo normally.&lt;/p&gt;

&lt;p&gt;Then zoom into the edited area.&lt;/p&gt;

&lt;p&gt;The first view tells you whether the image feels natural.&lt;/p&gt;

&lt;p&gt;The second helps you find small problems.&lt;/p&gt;

&lt;p&gt;Some backgrounds are much easier than others&lt;/p&gt;

&lt;p&gt;AI image removal works particularly well when there is useful information surrounding the object being removed.&lt;/p&gt;

&lt;p&gt;Simple backgrounds such as sky, walls, roads, sand, grass, and floors can be easier to reconstruct.&lt;/p&gt;

&lt;p&gt;Complicated backgrounds are more difficult.&lt;/p&gt;

&lt;p&gt;For example, imagine someone standing directly in front of a sign.&lt;/p&gt;

&lt;p&gt;The entire middle section of the sign is hidden by the person.&lt;/p&gt;

&lt;p&gt;There may be no visible information showing what was actually behind them.&lt;/p&gt;

&lt;p&gt;An AI model can create a plausible continuation, but it cannot know with certainty what was originally there.&lt;/p&gt;

&lt;p&gt;This is an important thing to understand about AI image editing.&lt;/p&gt;

&lt;p&gt;The goal is often to create something that looks natural, not to recover pixels that no longer exist.&lt;/p&gt;

&lt;p&gt;What I use for quick edits&lt;/p&gt;

&lt;p&gt;For a simple removal, I don't really want to open a full image editor and spend several minutes setting everything up.&lt;/p&gt;

&lt;p&gt;Usually I just want to upload an image, mark the unwanted area, and see the result.&lt;/p&gt;

&lt;p&gt;That's why I built ImgEraser.&lt;/p&gt;

&lt;p&gt;It focuses on one simple workflow: upload an image, brush over the person or object you want to remove, and let AI reconstruct the selected area.&lt;/p&gt;

&lt;p&gt;You can try it here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgeraser.com/" rel="noopener noreferrer"&gt;https://imgeraser.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The interesting part isn't just the AI model.&lt;/p&gt;

&lt;p&gt;The editing workflow matters too.&lt;/p&gt;

&lt;p&gt;If the selection is wrong, even a good model can produce a bad result.&lt;/p&gt;

&lt;p&gt;If the selection is accurate and there is enough surrounding information, the result can be much more convincing.&lt;/p&gt;

&lt;p&gt;What if the first result isn't good?&lt;/p&gt;

&lt;p&gt;Don't keep editing the already-edited image.&lt;/p&gt;

&lt;p&gt;Go back to the original.&lt;/p&gt;

&lt;p&gt;Then change the selection.&lt;/p&gt;

&lt;p&gt;If part of the person is still visible, make the selection slightly larger.&lt;/p&gt;

&lt;p&gt;If something nearby was accidentally changed, make it smaller.&lt;/p&gt;

&lt;p&gt;For complicated images, you can also think about different parts of the image separately instead of trying to solve everything with one enormous selection.&lt;/p&gt;

&lt;p&gt;And always keep the original&lt;/p&gt;

&lt;p&gt;I know this sounds obvious, but don't overwrite your original photo.&lt;/p&gt;

&lt;p&gt;Keep the original and save the edited version separately.&lt;/p&gt;

&lt;p&gt;That way you can compare the two versions or try another approach if the first result isn't what you wanted.&lt;/p&gt;

&lt;p&gt;The best AI edit is the one nobody notices&lt;/p&gt;

&lt;p&gt;This is probably the part of AI image editing that I find most interesting.&lt;/p&gt;

&lt;p&gt;The best result isn't necessarily the one that looks the most "AI generated."&lt;/p&gt;

&lt;p&gt;Actually, it should be the opposite.&lt;/p&gt;

&lt;p&gt;If someone looks at the finished photo and doesn't notice that anything was changed, the edit has done its job.&lt;/p&gt;

&lt;p&gt;That's the idea behind ImgEraser.&lt;/p&gt;

&lt;p&gt;Not trying to replace a professional image editor.&lt;/p&gt;

&lt;p&gt;Not trying to add hundreds of editing controls.&lt;/p&gt;

&lt;p&gt;Just making one annoying photo-editing task a little easier.&lt;/p&gt;

&lt;p&gt;If you have a photo with an unwanted person or object in it, try it with your own image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgeraser.com/" rel="noopener noreferrer"&gt;https://imgeraser.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm especially interested in the difficult cases.&lt;/p&gt;

&lt;p&gt;Crowded backgrounds, reflections, detailed patterns, overlapping people, text, and anything else that produces an unexpected result.&lt;/p&gt;

&lt;p&gt;Those cases are much more useful than a perfect example because they show where the experience still needs improvement.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Your AI Summary Is Not Evidence: Building a Timestamped YouTube Research Workflow</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Mon, 10 Aug 2026 11:41:20 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/your-ai-summary-is-not-evidence-building-a-timestamped-youtube-research-workflow-227p</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/your-ai-summary-is-not-evidence-building-a-timestamped-youtube-research-workflow-227p</guid>
      <description>&lt;p&gt;A two-hour interview can be easy to summarize and surprisingly hard to cite.&lt;/p&gt;

&lt;p&gt;That gap matters more than it first appears. A fluent AI summary may tell you what a video was about, but it does not automatically tell you where a claim came from, which words were actually spoken, or whether a number was copied correctly. For research, study notes, subtitle work, and content repurposing, the useful artifact is not just a summary. It is a summary with a path back to the source.&lt;/p&gt;

&lt;p&gt;This is the workflow for turning YouTube captions into evidence that can be searched, checked, and exported.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary and evidence are different artifacts
&lt;/h2&gt;

&lt;p&gt;A summary is an interpretation. A transcript is a record of the words available in a source. They solve different problems.&lt;/p&gt;

&lt;p&gt;When a model summarizes a long video, it may compress several statements into one sentence, omit a qualification, or merge two speakers. That can be fine for discovery. It is risky when the result becomes a quote, a statistic, a product requirement, or a paragraph that someone else needs to audit.&lt;/p&gt;

&lt;p&gt;The safest sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search the transcript to find the relevant passage.&lt;/li&gt;
&lt;li&gt;Read the surrounding caption segments.&lt;/li&gt;
&lt;li&gt;Jump to the original timestamp.&lt;/li&gt;
&lt;li&gt;Use the verified passage as the evidence for your summary.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Search in text, decide with context, verify in video.&lt;/p&gt;

&lt;p&gt;That extra loop is small. It is also the difference between the model saying something and the source supporting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the caption track
&lt;/h2&gt;

&lt;p&gt;YouTube videos do not all expose the same transcript data. A public video may have creator-uploaded captions, automatically generated captions, multiple language tracks, or no accessible caption track at all.&lt;/p&gt;

&lt;p&gt;A reliable workflow needs to preserve that distinction. Manual captions and automatic captions should not be presented as identical sources. Automatic captions are useful, but names, numbers, accents, and technical terms deserve a timestamp check.&lt;/p&gt;

&lt;p&gt;There is another important boundary. Retrieving an existing caption track is not the same as downloading the video or audio and running a new speech-to-text job. If the captions already exist, moving the text is usually faster, lighter, and easier to audit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://yttotext.com/" rel="noopener noreferrer"&gt;YT to Text&lt;/a&gt; is designed around that boundary. Paste a public YouTube URL, and it opens the caption track that YouTube makes available. There is no video or audio upload step.&lt;/p&gt;

&lt;p&gt;Videos can still be unavailable. Private, removed, restricted, still-live, or caption-free sources may not provide a usable transcript. Repeating the request cannot manufacture a missing track, so the honest result is an unavailable source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search exact phrases before broad topics
&lt;/h2&gt;

&lt;p&gt;Long transcripts become practical when they behave like searchable documents.&lt;/p&gt;

&lt;p&gt;Start with the most specific term you know, such as a surname, product name, uncommon technical phrase, or short quote. If it does not appear, shorten the query one word at a time. A narrow search gives you a smaller set of passages to verify.&lt;/p&gt;

&lt;p&gt;When a match appears, do not copy only the matching line. Read at least one segment before and after it. Captions are split for timing, not for meaning. The sentence you need may continue in the next cue, and the speaker's qualification may be in the previous one.&lt;/p&gt;

&lt;p&gt;This is also where transcript search beats scrubbing. A video player is a linear interface. Research is not. Searching the text lets you move directly to the relevant moment, then use the video for the final judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the timestamp attached
&lt;/h2&gt;

&lt;p&gt;A transcript without timing is convenient until you need to check it.&lt;/p&gt;

&lt;p&gt;Keep the original timestamp beside important passages. This lets you answer practical questions quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the speaker say 15 or 50?&lt;/li&gt;
&lt;li&gt;Was the claim about the product, the market, or a hypothetical?&lt;/li&gt;
&lt;li&gt;Did the sentence belong to the host or the guest?&lt;/li&gt;
&lt;li&gt;Was the word an industry term or an automatic-caption error?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Timestamp verification is not only for journalists. It helps students cite lectures, editors prepare subtitles, researchers collect quotes, and content teams turn interviews into accurate briefs.&lt;/p&gt;

&lt;p&gt;For a short note, a clean reading view is comfortable. For a claim that will be published, the timestamp is part of the evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick an export format that matches the next task
&lt;/h2&gt;

&lt;p&gt;TXT, SRT, and VTT are different working formats, not decorative file extensions.&lt;/p&gt;

&lt;p&gt;TXT is the simplest option for reading, notes, search, and AI prompts. Use it when the words matter more than timing.&lt;/p&gt;

&lt;p&gt;SRT is built for subtitle cues with sequence numbers and start/end times. It is a natural handoff to video editors and subtitle tools.&lt;/p&gt;

&lt;p&gt;VTT is designed for web video and the HTML track element. Use it when captions will be attached to a browser-based player.&lt;/p&gt;

&lt;p&gt;If you are preparing a transcript for an AI assistant, start with clean TXT for the main context and preserve a timestamped copy for verification. One source should be enough to produce all three exports. Re-submitting the same video just to change the format is unnecessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give AI an evidence layer
&lt;/h2&gt;

&lt;p&gt;A language model is useful after the source has been made searchable, not instead of that step.&lt;/p&gt;

&lt;p&gt;For a long interview, split the transcript into logical sections. Keep timestamps on claims that may need checking. Ask the model to separate direct evidence from interpretation and to cite the relevant timestamp when it makes a factual statement.&lt;/p&gt;

&lt;p&gt;A practical prompt can be simple:&lt;/p&gt;

&lt;p&gt;Summarize this section in five bullets. For every factual claim, include the source timestamp. Mark anything that is an interpretation or an uncertain automatic caption.&lt;/p&gt;

&lt;p&gt;The prompt is not a guarantee of accuracy. It is a reminder that the transcript remains the evidence layer and the model remains the analysis layer.&lt;/p&gt;

&lt;p&gt;There is a more detailed version of this workflow in &lt;a href="https://yttotext.com/guides/how-to-get-youtube-transcript-for-claude" rel="noopener noreferrer"&gt;this guide for preparing a YouTube transcript for Claude&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy belongs in the workflow
&lt;/h2&gt;

&lt;p&gt;Transcripts can contain client names, unpublished research, or quotes that are not ready to share. A useful tool should make the working state private by default.&lt;/p&gt;

&lt;p&gt;YT to Text keeps transcript workspaces private and removes anonymous job history after 24 hours. You can start without an account and complete up to three successful transcript requests per day. Failed requests do not consume the successful allowance.&lt;/p&gt;

&lt;p&gt;Those product limits are intentionally visible. A trustworthy workflow should tell you when it can retrieve captions, when it cannot, and what happens to the working data.&lt;/p&gt;

&lt;p&gt;You still need to respect copyright, platform rules, and the purpose of the original source. A transcript makes a video easier to analyze. It does not transfer ownership of the speaker's words.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reusable loop
&lt;/h2&gt;

&lt;p&gt;The complete process is short:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the public YouTube URL.&lt;/li&gt;
&lt;li&gt;Check that an accessible caption track exists.&lt;/li&gt;
&lt;li&gt;Note the language and whether the captions are manual or automatic.&lt;/li&gt;
&lt;li&gt;Search for an exact phrase.&lt;/li&gt;
&lt;li&gt;Read the surrounding context.&lt;/li&gt;
&lt;li&gt;Verify important lines at the original timestamp.&lt;/li&gt;
&lt;li&gt;Export TXT, SRT, or VTT for the next task.&lt;/li&gt;
&lt;li&gt;Keep the source URL with your notes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first-principles idea is to move the smallest useful representation of the source. If captions already exist, do not download a full video just to recover its words. Retrieve the text, preserve the timing relationship, and return to the source when accuracy matters.&lt;/p&gt;

&lt;p&gt;That is how a transcript stops being a wall of text and becomes a dependable research surface.&lt;/p&gt;

</description>
      <category>youtube</category>
      <category>ai</category>
      <category>research</category>
    </item>
    <item>
      <title>Why a YouTube transcript tool should preserve the path back to the source</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Sun, 09 Aug 2026 14:15:53 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/why-a-youtube-transcript-tool-should-preserve-the-path-back-to-the-source-7ab</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/why-a-youtube-transcript-tool-should-preserve-the-path-back-to-the-source-7ab</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4qtw71vtnsyh7zgvbglu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4qtw71vtnsyh7zgvbglu.png" alt="YT to Text transcript workspace" width="799" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a deceptively simple version of a YouTube transcript tool.&lt;/p&gt;

&lt;p&gt;Accept a URL. Fetch some caption text. Put the result in a large text box. Add a copy button. Done.&lt;/p&gt;

&lt;p&gt;That version works until someone tries to use the transcript for real work.&lt;/p&gt;

&lt;p&gt;A researcher finds a number but cannot tell where it appeared in the video. An editor receives plain text but needs timed cues. A student searches a long lecture and sees one match without the explanation around it. An automatic caption misspells a name, yet the interface gives no clue that the line should be checked.&lt;/p&gt;

&lt;p&gt;The difficult part is not turning captions into characters. It is preserving enough structure that those characters remain useful and trustworthy.&lt;/p&gt;

&lt;p&gt;That became the main product constraint behind &lt;a href="https://yttotext.com/" rel="noopener noreferrer"&gt;YT to Text&lt;/a&gt;, a private workspace for captions already available on public YouTube videos.&lt;/p&gt;

&lt;p&gt;This article explains the decisions behind that constraint and the broader engineering lesson they reveal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Begin with the user’s next action
&lt;/h2&gt;

&lt;p&gt;It is tempting to define a transcript product by its input and output.&lt;/p&gt;

&lt;p&gt;The input is a YouTube URL. The output is text.&lt;/p&gt;

&lt;p&gt;But users do not arrive because they enjoy transforming URLs into strings. They arrive because another task is blocked.&lt;/p&gt;

&lt;p&gt;They need to find a phrase in a lecture, verify a quote from an interview, copy a clean passage into notes, or import captions into an editing workflow.&lt;/p&gt;

&lt;p&gt;Once the next action becomes the unit of design, the product requirements change.&lt;/p&gt;

&lt;p&gt;Finding a phrase requires search and match navigation. Verifying a quote requires timestamps linked back to the source. Reading requires a clean text view. Editing requires a timed interchange format. Repeated work requires the successful result to remain available without fetching the source again.&lt;/p&gt;

&lt;p&gt;The central object is therefore not a text blob. It is an ordered collection of caption segments.&lt;/p&gt;

&lt;p&gt;Conceptually, each segment needs at least two pieces of information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TranscriptSegment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;startSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else can be derived from that representation.&lt;/p&gt;

&lt;p&gt;A readable transcript groups segments for scanning. A plain-text export joins the text. A timestamped view formats &lt;code&gt;startSeconds&lt;/code&gt;. Search maps matches back to their segment. SRT and VTT derive timed cues from the same ordered source.&lt;/p&gt;

&lt;p&gt;One retrieval. Multiple useful representations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search should preserve context
&lt;/h2&gt;

&lt;p&gt;A naive transcript search can return a count and highlight a matching substring. That is technically correct but often practically weak.&lt;/p&gt;

&lt;p&gt;Imagine searching a two-hour interview for a product name. The useful answer is not merely that the name appears four times. You need to move through those four moments, read the sentences around each match, and inspect the matching moment in the original video.&lt;/p&gt;

&lt;p&gt;This leads to three small but important rules.&lt;/p&gt;

&lt;p&gt;Search the source transcript without rewriting it. Keep an ordered list of match positions. Make every result reversible through its timestamp.&lt;/p&gt;

&lt;p&gt;The first rule prevents search from changing the evidence. The second makes next and previous navigation deterministic. The third keeps the transcript connected to the video.&lt;/p&gt;

&lt;p&gt;That final connection is easy to underestimate. A timestamp is not decorative metadata. It is a compact provenance link.&lt;/p&gt;

&lt;p&gt;When automatic captions turn a technical term into a plausible but incorrect word, the user needs a cheap way to check. A clickable timestamp makes verification one action. Removing timestamps makes verification a second research project.&lt;/p&gt;

&lt;h2&gt;
  
  
  TXT, SRT, and VTT are different jobs
&lt;/h2&gt;

&lt;p&gt;Export menus often present file formats as a cosmetic preference. In transcript workflows, each format represents a different job.&lt;/p&gt;

&lt;p&gt;TXT is for reading, notes, search, and copy. It may be plain or include human-readable timestamps.&lt;/p&gt;

&lt;p&gt;SRT is for editing and subtitle upload. It requires sequential cue numbers and time ranges in this form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
00:00:02,000 --&amp;gt; 00:00:06,000
The first caption line.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VTT is for web video and browser-oriented caption workflows. It begins with a &lt;code&gt;WEBVTT&lt;/code&gt; header and uses a period for milliseconds.&lt;br&gt;
&lt;/p&gt;

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

00:00:02.000 --&amp;gt; 00:00:06.000
The first caption line.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The formatting details look tiny, but tiny protocol differences are where supposedly convenient tools create expensive cleanup work.&lt;/p&gt;

&lt;p&gt;There is another edge case. Caption sources often provide the start of a segment without a clean, editor-friendly end time. A formatter still needs to create a valid cue range. The implementation should derive the end from the next segment when possible and enforce a readable minimum duration when consecutive start times collapse together.&lt;/p&gt;

&lt;p&gt;That behavior deserves direct tests. Formatting code is deterministic, easy to isolate, and likely to break downstream tools if it changes silently.&lt;/p&gt;

&lt;p&gt;The KISS version is not to build three export pipelines. It is to keep one validated segment model and three small pure formatters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation belongs at the boundary
&lt;/h2&gt;

&lt;p&gt;Media metadata from an upstream service should be treated as untrusted input, even when the service is yours.&lt;/p&gt;

&lt;p&gt;A successful-looking response can still contain an empty segment array, negative timestamps, missing language metadata, or a malformed job identifier. Allowing that state into the rest of the application spreads defensive checks across every component.&lt;/p&gt;

&lt;p&gt;The simpler approach is to validate once at the boundary and fail with useful context.&lt;/p&gt;

&lt;p&gt;The validated transcript job can then become the public interface used by search, rendering, download formatting, retention, and any private AI features. Those consumers should not reach into the extractor’s internal response shape.&lt;/p&gt;

&lt;p&gt;This keeps the modules loosely coupled. It also makes failures easier to understand.&lt;/p&gt;

&lt;p&gt;If retrieval failed, report a retrieval problem. If the video has no available captions, report that boundary. Do not return an empty transcript and hope the UI invents the correct explanation.&lt;/p&gt;

&lt;p&gt;An empty successful result is not graceful degradation. It is an ambiguous error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not claim capabilities the source cannot provide
&lt;/h2&gt;

&lt;p&gt;One of the most important product decisions is also one of the least technical.&lt;/p&gt;

&lt;p&gt;The free transcript workflow in YT to Text retrieves caption tracks already available for public YouTube videos. It does not generate a new transcription for a caption-free video. It does not present a machine translation as an original caption language. It does not make automatic captions more accurate by displaying them in a polished interface.&lt;/p&gt;

&lt;p&gt;Those limits need to appear in the product, not just in an internal architecture diagram.&lt;/p&gt;

&lt;p&gt;Private, removed, restricted, still-live, and caption-free videos may not produce a transcript. Available languages depend on the caption tracks YouTube provides. Automatic captions can contain errors in names, numbers, punctuation, accents, and technical vocabulary.&lt;/p&gt;

&lt;p&gt;The interface labels the selected caption source and encourages timestamp verification for important lines.&lt;/p&gt;

&lt;p&gt;This is a general design rule for tools built on upstream data.&lt;/p&gt;

&lt;p&gt;Expose the boundary where users make decisions.&lt;/p&gt;

&lt;p&gt;If the source is probabilistic or imperfect, hiding that fact does not remove uncertainty. It transfers the uncertainty to the user at the worst possible moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy follows data minimization
&lt;/h2&gt;

&lt;p&gt;A public YouTube link is public. A person’s collection of researched videos is not necessarily public.&lt;/p&gt;

&lt;p&gt;The caption retrieval task does not require uploading the video or audio. It needs the public video identifier and the available caption source. Collecting the media file would create more transfer, storage, and privacy risk without helping the current task.&lt;/p&gt;

&lt;p&gt;So the product does not request a video upload.&lt;/p&gt;

&lt;p&gt;The generated workspace is private and is not exposed as a public SEO page. Anonymous history is removed after 24 hours. Private workspace and profile routes should remain outside search indexing.&lt;/p&gt;

&lt;p&gt;This choice is useful beyond privacy. It also keeps the public site’s information architecture honest. Public pages explain the product and its formats. Private pages contain user work. Mixing the two can leak user activity while filling search indexes with thin, duplicated transcript pages.&lt;/p&gt;

&lt;p&gt;Data minimization is often presented as a compliance exercise. Here it is also the simplest architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Count delivered value, not attempts
&lt;/h2&gt;

&lt;p&gt;YT to Text currently allows three successful transcript jobs before sign-in, and a free account receives five successful jobs per day.&lt;/p&gt;

&lt;p&gt;The important word is successful.&lt;/p&gt;

&lt;p&gt;An invalid URL, an unavailable video, missing captions, or a service failure does not deliver the thing the user came for. Counting that attempt against the allowance would be easy to implement and hard to justify.&lt;/p&gt;

&lt;p&gt;Usage therefore needs to be reserved and recorded around a successful outcome, with idempotency protecting retries from being counted twice.&lt;/p&gt;

&lt;p&gt;This is another case where the product principle shapes the data model. If billing or quota state is attached to button clicks, network retries and partial failures become user-facing unfairness. If it is attached to a durable successful job, the rules become much easier to explain.&lt;/p&gt;

&lt;p&gt;The third successful transcript also remains usable. A quota boundary should prevent a new job, not retroactively damage the result that crossed the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make transformations traceable
&lt;/h2&gt;

&lt;p&gt;Transcript tools sit inside a larger family of products that compress or transform source material. Summarizers, document parsers, search indexes, analytics dashboards, and AI assistants all do some version of this.&lt;/p&gt;

&lt;p&gt;The shared danger is losing the route back.&lt;/p&gt;

&lt;p&gt;A summary without citations is quick but difficult to audit. A chart without access to underlying values is persuasive but fragile. A transcript without timestamps is readable but detached from the thing it represents.&lt;/p&gt;

&lt;p&gt;The resulting design principle is simple.&lt;/p&gt;

&lt;p&gt;Every useful transformation should preserve a cheap path back to its source.&lt;/p&gt;

&lt;p&gt;In YT to Text, that path is a timestamp. In another product it may be a document page, a database row, a request identifier, or a source URL. The mechanism changes. The trust model does not.&lt;/p&gt;

&lt;p&gt;Compression gives users speed. Traceability gives them agency.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical test
&lt;/h2&gt;

&lt;p&gt;If you are building a transcript or media tool, take one real user task and follow it past the first successful response.&lt;/p&gt;

&lt;p&gt;Do not stop when text appears on the screen.&lt;/p&gt;

&lt;p&gt;Can the user find one exact phrase in a long result? Can they see enough surrounding context to interpret it? Can they reopen the corresponding source moment? Can they export a format their next tool understands? Can they tell when the upstream caption may be incomplete or inaccurate? Does a failed request consume their quota? Is private work accidentally becoming public content?&lt;/p&gt;

&lt;p&gt;Those questions reveal more product quality than the size of the text box.&lt;/p&gt;

&lt;p&gt;You can try the current workflow at &lt;a href="https://yttotext.com/" rel="noopener noreferrer"&gt;YT to Text&lt;/a&gt;. Paste a public YouTube video with available captions, search for something specific, open a matching timestamp, and export the transcript in the format your next task needs. The first three successful transcripts do not require sign-in.&lt;/p&gt;

&lt;p&gt;The output is text.&lt;/p&gt;

&lt;p&gt;The product is the path that keeps the text useful.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>youtube</category>
      <category>subtitles</category>
    </item>
    <item>
      <title>Building a Privacy-First Face Analyzer That Runs in the Browser</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Fri, 31 Jul 2026 07:42:54 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/building-a-privacy-first-face-analyzer-that-runs-in-the-browser-j5g</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/building-a-privacy-first-face-analyzer-that-runs-in-the-browser-j5g</guid>
      <description>&lt;p&gt;Most AI face analysis products begin with a simple architecture: upload a photo, send it to an API, run a model on a server, and return a score.&lt;/p&gt;

&lt;p&gt;That approach is convenient, but it creates a data problem. A face photo is personal data, and many people do not want to upload it just to learn about visible proportions or face shape. When I built &lt;a href="https://lookblueprint.com/" rel="noopener noreferrer"&gt;LookBlueprint&lt;/a&gt;, I started with a different constraint:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The useful first result should be possible without sending the original photo to a server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This constraint shaped almost every technical decision in the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the first analysis locally
&lt;/h2&gt;

&lt;p&gt;The free analysis runs in the browser using MediaPipe Face Landmarker and a bundled WebAssembly runtime. The model and runtime are shipped with the application, and the analysis work runs in a Web Worker so the interface remains responsive while landmarks are being detected.&lt;/p&gt;

&lt;p&gt;The flow is roughly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user selects one clear, front-facing photo.&lt;/li&gt;
&lt;li&gt;The browser validates and prepares the image for analysis.&lt;/li&gt;
&lt;li&gt;The worker detects facial landmarks and basic image-quality signals.&lt;/li&gt;
&lt;li&gt;Pure TypeScript analysis functions convert those landmarks into a versioned face-analysis snapshot.&lt;/li&gt;
&lt;li&gt;The UI renders the snapshot into focused results and educational explanations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The original image is not uploaded for the free result. There is no login requirement for that workflow, and the result is kept temporarily in the browser session.&lt;/p&gt;

&lt;p&gt;This is a practical privacy improvement, but it also changes the engineering trade-offs. The model must load reliably on a range of devices, image processing must fit within browser memory limits, and errors must be understandable to a person who is not debugging a machine-learning pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring structure instead of pretending to measure beauty
&lt;/h2&gt;

&lt;p&gt;A face-analysis score can sound more objective than it really is. I wanted the product to expose measurable references without presenting them as a universal ranking of people.&lt;/p&gt;

&lt;p&gt;LookBlueprint separates several types of output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visible left-to-right symmetry and pair differences.&lt;/li&gt;
&lt;li&gt;Face-shape candidates with confidence percentages.&lt;/li&gt;
&lt;li&gt;Vertical thirds and horizontal fifths.&lt;/li&gt;
&lt;li&gt;Face length-to-width and facial width-to-height ratios.&lt;/li&gt;
&lt;li&gt;Eye-shape references and canthal tilt.&lt;/li&gt;
&lt;li&gt;Jawline and lower-face structure.&lt;/li&gt;
&lt;li&gt;A separate photo-reliability signal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The overall rating is not treated as a scientific truth. It combines selected structural signals, while camera reliability remains separate. Natural asymmetry, lighting, head rotation, expression, lens distortion, and landmark quality all affect what can be measured in one photo.&lt;/p&gt;

&lt;p&gt;That distinction matters. A result should help someone understand the image in front of them, not tell them what they are worth or reduce a complex person to a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardest part is measurement quality
&lt;/h2&gt;

&lt;p&gt;Face landmarks are not the same thing as ground truth. A small change in camera angle can change the apparent proportions. Hair can hide the jawline. Low light can reduce landmark stability. A wide-angle selfie can exaggerate the center of the face.&lt;/p&gt;

&lt;p&gt;Because of that, the interface explains limitations next to the result instead of hiding them in a legal page. It reports confidence, identifies the photo conditions that affect interpretation, and encourages users to treat the output as a reference from that image.&lt;/p&gt;

&lt;p&gt;This also makes the error states important. If a photo does not contain a usable face, the user needs a clear next step: try a brighter, more front-facing image with the face unobstructed. A generic "analysis failed" message is technically accurate but not useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small architecture with explicit boundaries
&lt;/h2&gt;

&lt;p&gt;The application uses a Next.js frontend, a Web Worker for the browser analysis pipeline, and pure analysis modules that transform landmarks into a typed, versioned FaceAnalysisSnapshot.&lt;/p&gt;

&lt;p&gt;That snapshot is the boundary between detection and presentation. The worker does not need to know how a result card is rendered, and the UI does not need to know how the model locates landmarks. This keeps the core calculations testable and makes it possible to add focused tools without duplicating the detection pipeline.&lt;/p&gt;

&lt;p&gt;The product currently includes focused views for rating, symmetry, face shape, proportions, canthal tilt, jawline, eye shape, and a neutral structure view. It also includes guides covering methodology, research references, accuracy, bias, limitations, privacy, and practical style questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when a user wants a report?
&lt;/h2&gt;

&lt;p&gt;The free analysis stays in the browser. For the optional personalized Face Blueprint report, the browser prepares a constrained analysis copy for the report workflow rather than sending the original source file. The copy is limited in size and dimensions, and the report can be delivered as an on-page result, a multi-page PDF, and a keepsake image.&lt;/p&gt;

&lt;p&gt;The important product decision is to make that boundary visible. Users should know which workflow is local, which workflow is optional, and what kind of image copy is involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why build it this way?
&lt;/h2&gt;

&lt;p&gt;The first-principles answer is simple: if the product only needs facial landmarks and ratios for its free result, uploading the original photo is not a necessary starting assumption.&lt;/p&gt;

&lt;p&gt;Local processing does not solve every privacy or accuracy problem. It does, however, reduce unnecessary transmission, give users a clearer mental model, and force the product to be honest about what one photo can and cannot tell us.&lt;/p&gt;

&lt;p&gt;You can try the free browser-based analyzer at &lt;a href="https://lookblueprint.com/" rel="noopener noreferrer"&gt;lookblueprint.com&lt;/a&gt;. The goal is not to produce a definitive judgment. It is to give people a private, understandable way to explore the visible patterns in a photograph.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>privacy</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building a Low-Cost AI Brainrot Video Pipeline on Cloudflare</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Sat, 18 Jul 2026 10:13:19 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/building-a-low-cost-ai-brainrot-video-pipeline-on-cloudflare-5ego</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/building-a-low-cost-ai-brainrot-video-pipeline-on-cloudflare-5ego</guid>
      <description>&lt;p&gt;Short-form video tools often look like a single button, but the product experience is really a small distributed system. A useful pipeline has to accept messy inputs, turn them into a script, generate media, report progress clearly, and keep the cost of each render predictable.&lt;/p&gt;

&lt;p&gt;I am building &lt;a href="https://brainrotkit.com" rel="noopener noreferrer"&gt;BrainrotKit&lt;/a&gt; around that problem. The goal is not to hide the workflow behind a magic button. The goal is to make each stage understandable enough that a creator can review the result and decide whether it is worth generating the next stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start in the browser when the browser is good enough
&lt;/h2&gt;

&lt;p&gt;PDF parsing and OCR do not always need a paid server. A browser can extract text from many PDFs locally, and an OCR library can handle image-based notes without sending the original file to a backend. This reduces latency, lowers storage requirements, and gives users a simpler privacy story.&lt;/p&gt;

&lt;p&gt;The important boundary is quality. If the browser cannot confidently extract the content, the UI should say so and ask for a clearer file. Quietly passing a bad OCR result to a language model only creates a more expensive bad result.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Separate planning from media generation
&lt;/h2&gt;

&lt;p&gt;The text model should produce a compact, structured plan before an image or video model is called. A useful intermediate object contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a one-sentence hook;&lt;/li&gt;
&lt;li&gt;the narration script;&lt;/li&gt;
&lt;li&gt;a visual description for each beat;&lt;/li&gt;
&lt;li&gt;the target duration;&lt;/li&gt;
&lt;li&gt;a short caption and title.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation makes retries cheaper. If the image looks wrong, regenerate the image prompt instead of paying to rewrite the entire script. It also makes the system easier to debug because every output has a visible input.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use a job state machine, not a spinner
&lt;/h2&gt;

&lt;p&gt;Media generation is asynchronous. A request can move through "queued", "planning", "generating-image", "generating-voice", "generating-video", "ready", or "failed". The frontend should display the current stage, an estimated next action, and a useful error when something goes wrong.&lt;/p&gt;

&lt;p&gt;A generic spinner is not enough for a ten-second wait. Users should know whether the system is still planning, waiting for a provider, or retrying a failed asset. This is also where idempotency matters: refreshing the page must not create a second paid job accidentally.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Track credits at the product layer
&lt;/h2&gt;

&lt;p&gt;Provider prices change, so the product should not expose raw API costs as if they were permanent. I prefer a simple credit ledger: each operation reserves credits, the worker records the actual result, and unused reservations are released when a job fails before generation.&lt;/p&gt;

&lt;p&gt;Short drafts are useful here. A five-second render is a cheaper way to validate a prompt than a fifteen-second render, and the UI can make that tradeoff explicit before the user starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Keep the user in the review loop
&lt;/h2&gt;

&lt;p&gt;An AI video is not finished just because the provider returned a file. The creator still needs to check pronunciation, pacing, captions, and whether the visual actually supports the idea. The product should make preview, download, retry, and delete actions obvious.&lt;/p&gt;

&lt;p&gt;That is the thinking behind &lt;a href="https://brainrotkit.com" rel="noopener noreferrer"&gt;BrainrotKit&lt;/a&gt;: a focused AI brainrot video workflow for turning text, notes, and PDFs into short-form concepts while keeping the intermediate steps visible. The meme layer can be playful; the engineering underneath should be boring, observable, and cost-aware.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical first release
&lt;/h2&gt;

&lt;p&gt;For a small team, the first production version can stay intentionally narrow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;browser-side extraction for text and PDFs;&lt;/li&gt;
&lt;li&gt;one text model for script planning;&lt;/li&gt;
&lt;li&gt;one image provider and one video provider;&lt;/li&gt;
&lt;li&gt;durable project and credit records;&lt;/li&gt;
&lt;li&gt;clear loading states and failure messages;&lt;/li&gt;
&lt;li&gt;a short export before longer generations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is enough to learn where users actually get stuck. The first principle is to measure the complete user journey, not just whether an individual model call succeeded. When the workflow is understandable, creators can make better decisions and the platform can improve without wasting their budget on invisible retries.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cloud</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Small Static Game Wiki with Next.js and Cloudflare Workers</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Wed, 01 Jul 2026 03:35:02 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/building-a-small-static-game-wiki-with-nextjs-and-cloudflare-workers-4f27</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/building-a-small-static-game-wiki-with-nextjs-and-cloudflare-workers-4f27</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5p592u61ziy5fcvrl1c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5p592u61ziy5fcvrl1c.png" alt="CookieRun Classic Wiki guide hub" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently put together a small static guide hub for CookieRun Classic: &lt;a href="https://crclassic.wiki/" rel="noopener noreferrer"&gt;CRClassic.wiki&lt;/a&gt;. It is a simple content site, but the deployment path had a few details that are easy to miss when combining Next.js static export, Cloudflare Workers Static Assets, sitemap generation, and game-guide SEO.&lt;/p&gt;

&lt;p&gt;This is a short technical note on what ended up mattering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The site shape
&lt;/h2&gt;

&lt;p&gt;The site is not an app dashboard. It is a focused information hub with separate pages for player intent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;codes and redeem steps&lt;/li&gt;
&lt;li&gt;beginner route&lt;/li&gt;
&lt;li&gt;cookie, pet, and treasure tier pages&lt;/li&gt;
&lt;li&gt;PC setup&lt;/li&gt;
&lt;li&gt;reroll and meta notes&lt;/li&gt;
&lt;li&gt;a central wiki hub&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;That split helps because each URL answers a different question. A player searching for a redeem flow does not need to land on the same page as someone comparing Cookie upgrades.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static export was the right fit
&lt;/h2&gt;

&lt;p&gt;For this kind of site, static output keeps the deployment 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="c1"&gt;// next.config.mjs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;export&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;unoptimized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Cloudflare Workers config points directly at the generated &lt;code&gt;out&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cpclassic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compatibility_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assets"&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;"directory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"html_handling"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auto-trailing-slash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"not_found_handling"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"404-page"&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;h2&gt;
  
  
  The small metadata details mattered
&lt;/h2&gt;

&lt;p&gt;The site needed normal page metadata, but also page-specific Open Graph images so each guide has a relevant preview instead of one generic logo.&lt;/p&gt;

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

&lt;p&gt;A few details I made sure to include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;canonical URLs for every route&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;robots.txt&lt;/code&gt; and &lt;code&gt;sitemap.xml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max-image-preview: large&lt;/code&gt; for Google image previews&lt;/li&gt;
&lt;li&gt;page-specific &lt;code&gt;og:image&lt;/code&gt; and Twitter card images&lt;/li&gt;
&lt;li&gt;a real favicon instead of relying only on 32px PNG files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The public sitemap is here: &lt;a href="https://crclassic.wiki/sitemap.xml" rel="noopener noreferrer"&gt;https://crclassic.wiki/sitemap.xml&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Content pages need more than lists
&lt;/h2&gt;

&lt;p&gt;For game-guide pages, a plain tier table is rarely enough. The more useful structure is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who should use this option&lt;/li&gt;
&lt;li&gt;when to upgrade it&lt;/li&gt;
&lt;li&gt;when to stop investing&lt;/li&gt;
&lt;li&gt;what mistakes to avoid&lt;/li&gt;
&lt;li&gt;what related page to read next&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;That is the approach I used on the &lt;a href="https://crclassic.wiki/cookie-run-classic-tier-list" rel="noopener noreferrer"&gt;Cookie Run Classic Tier List&lt;/a&gt;: the page includes rankings, but the real value is upgrade logic and account-stage guidance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;For a small wiki, the technical stack can stay boring. The hard part is making each page answer a real search intent and making sure crawlers can clearly see the URL, canonical, sitemap entry, image preview, and internal links.&lt;/p&gt;

&lt;p&gt;If you are building a similar static guide hub, I would start with the content architecture first, then wire the metadata and deployment around that structure.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>cookierun</category>
      <category>gamechallenge</category>
    </item>
    <item>
      <title>How I Built a Fan Guide Hub for BOMBANANA!</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:23:19 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/how-i-built-a-fan-guide-hub-for-bombanana-3ga7</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/how-i-built-a-fan-guide-hub-for-bombanana-3ga7</guid>
      <description>&lt;p&gt;I recently put together a fan guide hub for &lt;strong&gt;BOMBANANA!&lt;/strong&gt;, a new co-op bomb defusal game where three players have to coordinate under different communication limits.&lt;/p&gt;

&lt;p&gt;The site is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bombanana.online/" rel="noopener noreferrer"&gt;https://bombanana.online/&lt;/a&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;The game is funny and chaotic, but the learning curve is mostly about communication. New players do not only need a list of rules. They need a content structure that answers practical questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What does each role do?&lt;/li&gt;
&lt;li&gt;Which player should speak first?&lt;/li&gt;
&lt;li&gt;How should the team describe modules?&lt;/li&gt;
&lt;li&gt;What should a beginner team practice in the first session?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That shaped the site architecture more than keyword research alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content structure
&lt;/h2&gt;

&lt;p&gt;I split the guide into a few core sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Role guides: &lt;a href="https://bombanana.online/roles" rel="noopener noreferrer"&gt;https://bombanana.online/roles&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Module guides: &lt;a href="https://bombanana.online/modules" rel="noopener noreferrer"&gt;https://bombanana.online/modules&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;First-session guide: &lt;a href="https://bombanana.online/beginners/how-to-play" rel="noopener noreferrer"&gt;https://bombanana.online/beginners/how-to-play&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Team callouts: &lt;a href="https://bombanana.online/communication/team-callouts" rel="noopener noreferrer"&gt;https://bombanana.online/communication/team-callouts&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to make the site useful during actual gameplay, not just readable as a long article.&lt;/p&gt;

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

&lt;p&gt;For a small game guide site, the best pages are not always generic “ultimate guide” pages. The highest-value pages are often specific resources that players can use immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a checklist&lt;/li&gt;
&lt;li&gt;a module walkthrough&lt;/li&gt;
&lt;li&gt;role-specific advice&lt;/li&gt;
&lt;li&gt;short callout systems&lt;/li&gt;
&lt;li&gt;screenshots that match the game state&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;I am continuing to improve the guide with more screenshots, better internal links, and clearer module pages as more players search for help around the demo and full release.&lt;/p&gt;

&lt;p&gt;If you are building a small content site for a game, my main takeaway is simple: organize by the player’s next decision, not by what looks like a clean blog category.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>gamedev</category>
      <category>seo</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Built a Task Bar Hero Item Database and Drop Finder</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Tue, 09 Jun 2026 09:37:49 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/i-built-a-task-bar-hero-item-database-and-drop-finder-5cbd</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/i-built-a-task-bar-hero-item-database-and-drop-finder-5cbd</guid>
      <description>&lt;h1&gt;
  
  
  I Built a Wiki for Task Bar Hero After Getting Tired of Looking Up Item Drops
&lt;/h1&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%2Fk3uy6nprj189fayp3ahf.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%2Fk3uy6nprj189fayp3ahf.png" alt=" " width="800" height="380"&gt;&lt;/a&gt;&lt;br&gt;
While playing Task Bar Hero, I kept running into the same questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where does this item drop?&lt;/li&gt;
&lt;li&gt;Is it tradable?&lt;/li&gt;
&lt;li&gt;Does it actually sell on Steam?&lt;/li&gt;
&lt;li&gt;Is farming it worth the time?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The information existed, but it was scattered across Discord messages, Steam discussions, and spreadsheets.&lt;/p&gt;

&lt;p&gt;So I decided to build a dedicated wiki and tool site.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;I wanted a single place where players could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;search any item&lt;/li&gt;
&lt;li&gt;see drop locations&lt;/li&gt;
&lt;li&gt;check Steam Market availability&lt;/li&gt;
&lt;li&gt;compare farming routes&lt;/li&gt;
&lt;li&gt;access item-specific tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project eventually became:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://taskbarhero.nanobananas.me" rel="noopener noreferrer"&gt;https://taskbarhero.nanobananas.me&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Item Database
&lt;/h2&gt;

&lt;p&gt;The first challenge was structuring item data.&lt;/p&gt;

&lt;p&gt;Every item page needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rarity&lt;/li&gt;
&lt;li&gt;level&lt;/li&gt;
&lt;li&gt;category&lt;/li&gt;
&lt;li&gt;drop sources&lt;/li&gt;
&lt;li&gt;market status&lt;/li&gt;
&lt;li&gt;related items&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of writing pages manually, I generated them from a structured dataset and created static routes with Next.js.&lt;/p&gt;

&lt;p&gt;Example item page:&lt;/p&gt;

&lt;p&gt;/items/minor-ruby&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Drop Finder
&lt;/h2&gt;

&lt;p&gt;The most-used feature became the Drop Finder.&lt;/p&gt;

&lt;p&gt;Instead of navigating through multiple pages, players can search an item directly and immediately see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;where it drops&lt;/li&gt;
&lt;li&gt;which stages contain it&lt;/li&gt;
&lt;li&gt;farming options&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ended up being much faster than searching through community posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Steam Market Pages
&lt;/h2&gt;

&lt;p&gt;One unexpected SEO issue involved Steam Market data.&lt;/p&gt;

&lt;p&gt;Some items are technically tradable but have no active market listings.&lt;/p&gt;

&lt;p&gt;Initially I generated market pages for every item.&lt;/p&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;thousands of low-value pages&lt;/li&gt;
&lt;li&gt;thin content&lt;/li&gt;
&lt;li&gt;poor index quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To solve this, I split pages into two categories:&lt;/p&gt;

&lt;h3&gt;
  
  
  Indexed
&lt;/h3&gt;

&lt;p&gt;Items with actual Steam Market data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Noindexed
&lt;/h3&gt;

&lt;p&gt;Items without meaningful market information.&lt;/p&gt;

&lt;p&gt;Users can still access those pages, but search engines are instructed not to index them.&lt;/p&gt;

&lt;p&gt;This significantly improved the quality of pages submitted through the sitemap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multilingual SEO
&lt;/h2&gt;

&lt;p&gt;The site also supports multiple languages.&lt;/p&gt;

&lt;p&gt;One challenge was that English uses the root path:&lt;/p&gt;

&lt;p&gt;/items/minor-ruby&lt;/p&gt;

&lt;p&gt;while other languages use locale prefixes:&lt;/p&gt;

&lt;p&gt;/zh/items/minor-ruby&lt;/p&gt;

&lt;p&gt;To avoid duplicate-content issues I implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;canonical URLs&lt;/li&gt;
&lt;li&gt;hreflang alternates&lt;/li&gt;
&lt;li&gt;locale-specific sitemaps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps language versions connected while allowing search engines to understand the preferred URL structure.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Static Generation&lt;/li&gt;
&lt;li&gt;XML Sitemap Generation&lt;/li&gt;
&lt;li&gt;Structured Data&lt;/li&gt;
&lt;li&gt;Canonical URLs&lt;/li&gt;
&lt;li&gt;Hreflang Alternates&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd Improve Next
&lt;/h2&gt;

&lt;p&gt;The current roadmap includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better farming calculators&lt;/li&gt;
&lt;li&gt;richer item relationships&lt;/li&gt;
&lt;li&gt;drop-rate analytics&lt;/li&gt;
&lt;li&gt;player-contributed data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project started as a small utility for myself, but it's becoming a useful resource for other players as well.&lt;/p&gt;

&lt;p&gt;If you play Task Bar Hero, I'd love to hear what information you find hardest to locate in-game.&lt;br&gt;
That meant internal links, sitemap URLs, canonical URLs, and language switching all had to follow the same rule.&lt;/p&gt;

&lt;p&gt;Why I Built It&lt;br&gt;
Game wiki pages are useful, but I wanted something more decision-focused:&lt;/p&gt;

&lt;p&gt;“Where should I farm this?”&lt;br&gt;
“Does it have a market price?”&lt;br&gt;
“What drops it?”&lt;br&gt;
“What item should I compare it with?”&lt;br&gt;
So the site is more like a database plus a set of small tools.&lt;/p&gt;

&lt;p&gt;What I’m Improving Next&lt;br&gt;
Next things I want to improve:&lt;/p&gt;

&lt;p&gt;better farming route recommendations&lt;br&gt;
more market confidence signals&lt;br&gt;
cleaner guide pages&lt;br&gt;
more item comparison tools&lt;br&gt;
better visual summaries for new players&lt;br&gt;
If you play Task Bar Hero or like game-data tools, feedback is welcome.&lt;/p&gt;

&lt;p&gt;Main site:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://taskbarhero.nanobananas.me/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=500uv_week" rel="noopener noreferrer"&gt;https://taskbarhero.nanobananas.me/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=500uv_week&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title># I Built a Cleaner Online Grid Maker Because Most Existing Tools Feel Outdated</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Tue, 19 May 2026 09:11:01 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/-i-built-a-cleaner-online-grid-maker-because-most-existing-tools-feel-outdated-269a</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/-i-built-a-cleaner-online-grid-maker-because-most-existing-tools-feel-outdated-269a</guid>
      <description>&lt;p&gt;I’ve been practicing drawing and reference transfers recently, and I kept running into the same issue:&lt;/p&gt;

&lt;p&gt;Most online grid makers either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;feel outdated&lt;/li&gt;
&lt;li&gt;have poor mobile UX&lt;/li&gt;
&lt;li&gt;generate blurry grids&lt;/li&gt;
&lt;li&gt;overload the UI with unnecessary controls&lt;/li&gt;
&lt;li&gt;or make exporting painful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I started building my own browser-based grid tool focused on simplicity and speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I wanted to improve
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Better visual clarity
&lt;/h3&gt;

&lt;p&gt;A lot of existing tools stack too many settings at once.&lt;/p&gt;

&lt;p&gt;I wanted a cleaner workflow where users can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload an image&lt;/li&gt;
&lt;li&gt;Adjust grid size&lt;/li&gt;
&lt;li&gt;Export instantly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without needing to learn the interface first.&lt;/p&gt;




&lt;h3&gt;
  
  
  Faster canvas rendering
&lt;/h3&gt;

&lt;p&gt;One issue I noticed was that large images + dynamic grids can get laggy quickly in the browser.&lt;/p&gt;

&lt;p&gt;I spent some time optimizing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;canvas redraw behavior&lt;/li&gt;
&lt;li&gt;grid overlay rendering&lt;/li&gt;
&lt;li&gt;export sharpness&lt;/li&gt;
&lt;li&gt;responsive scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Especially for large portrait references.&lt;/p&gt;




&lt;h3&gt;
  
  
  Better use cases beyond drawing
&lt;/h3&gt;

&lt;p&gt;Interestingly, people started using it for things I didn’t initially expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instagram grid planning&lt;/li&gt;
&lt;li&gt;pixel art references&lt;/li&gt;
&lt;li&gt;crochet patterns&lt;/li&gt;
&lt;li&gt;classroom worksheets&lt;/li&gt;
&lt;li&gt;tattoo stencil prep&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which pushed me to make the layout system more flexible.&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%2Fo2evbw9icbmonmurx2vj.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%2Fo2evbw9icbmonmurx2vj.png" alt=" " width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  UX Lessons I Learned
&lt;/h2&gt;

&lt;p&gt;One thing I underestimated:&lt;/p&gt;

&lt;p&gt;Users care more about &lt;em&gt;clarity&lt;/em&gt; than features.&lt;/p&gt;

&lt;p&gt;Removing friction improved the experience far more than adding advanced options.&lt;/p&gt;

&lt;p&gt;Things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer buttons&lt;/li&gt;
&lt;li&gt;clearer spacing&lt;/li&gt;
&lt;li&gt;instant preview updates&lt;/li&gt;
&lt;li&gt;cleaner typography&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;made a bigger difference than “power features”.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current Direction
&lt;/h2&gt;

&lt;p&gt;Right now I’m experimenting with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better mobile support&lt;/li&gt;
&lt;li&gt;template presets&lt;/li&gt;
&lt;li&gt;sharper exports&lt;/li&gt;
&lt;li&gt;dark mode improvements&lt;/li&gt;
&lt;li&gt;reusable layouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still a work in progress, but it’s been fun building something actually useful for creators.&lt;/p&gt;

&lt;p&gt;For anyone curious, the current version is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gridmakeronline.com/" rel="noopener noreferrer"&gt;https://gridmakeronline.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love feedback from people who work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;drawing workflows&lt;/li&gt;
&lt;li&gt;canvas tools&lt;/li&gt;
&lt;li&gt;image processing&lt;/li&gt;
&lt;li&gt;creator utilities&lt;/li&gt;
&lt;li&gt;frontend performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Curious what features you think these kinds of tools still lack today.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>ux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From Side Project to $500/Month: Building a YouTube Tool as a Solo Developer</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Mon, 09 Mar 2026 03:52:06 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/from-side-project-to-500month-building-a-youtube-tool-as-a-solo-developer-8ok</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/from-side-project-to-500month-building-a-youtube-tool-as-a-solo-developer-8ok</guid>
      <description>&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%2F40u7vk2s714corl59x64.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%2F40u7vk2s714corl59x64.png" alt=" " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built a YouTube subtitle downloader as a side project. After 6 months and a lot of trial and error, I'm finally hitting $500/month. Here is the raw truth about what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎬 The Origin Story
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;March 2025:&lt;/strong&gt; I was working on a video project and needed to extract subtitles from 50+ YouTube videos. Manual copying? No way.&lt;/p&gt;

&lt;p&gt;I spent 2 hours building a quick Python script to automate it. It worked perfectly. Then I thought: &lt;em&gt;"Others probably need this too."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spoiler:&lt;/strong&gt; That assumption was both right and wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 The Idea Validation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What I did right:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Searched "youtube subtitle download" - 10K+ monthly searches. ✅&lt;/li&gt;
&lt;li&gt;Checked competitors - they existed (market validation!). ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I did wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Didn't talk to potential users before writing code. ❌&lt;/li&gt;
&lt;li&gt;Assumed people would pay immediately. ❌&lt;/li&gt;
&lt;li&gt;Ignored the "Red Ocean" warning signs. ❌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson 1:&lt;/strong&gt; Competition = validation, but you need a unique angle to stand out.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Building the MVP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Week 1-2:&lt;/strong&gt; Basic functionality&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input URL → Download subtitle&lt;/li&gt;
&lt;li&gt;Tech: Next.js + Python + Vercel&lt;/li&gt;
&lt;li&gt;Cost: $0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Week 3-4:&lt;/strong&gt; The "Feature Creep" mistake&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added 5 different export formats&lt;/li&gt;
&lt;li&gt;Fancy animations&lt;/li&gt;
&lt;li&gt;Dark mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Users:&lt;/strong&gt; 0&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 2:&lt;/strong&gt; Ship fast, iterate faster. Nobody cares about dark mode if your core value isn't hitting the mark.&lt;/p&gt;

&lt;h2&gt;
  
  
  📉 The Reality Check
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Month 1 Results:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Traffic: 50 visitors/day (mostly bots)&lt;/li&gt;
&lt;li&gt;Revenue: $0&lt;/li&gt;
&lt;li&gt;Time spent: 80 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; I built a "better mousetrap" in a market full of them. I needed to pivot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Turning Point:&lt;/strong&gt; I added &lt;strong&gt;AI-powered summarization&lt;/strong&gt;. Suddenly, I wasn't just a "downloader"—I was an AI productivity tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Monetization Strategy
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What didn't work:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Paywalls:&lt;/strong&gt; 95% bounce rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$9.99/mo subscription:&lt;/strong&gt; Too expensive for casual users.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What worked:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Freemium:&lt;/strong&gt; First download is free (no signup). This reduced friction by 80%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit system:&lt;/strong&gt; Pay-as-you-go model. Users love flexibility.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Conversion rate:&lt;/strong&gt; 2.5% (Industry average is 2-5%)&lt;/p&gt;

&lt;h2&gt;
  
  
  📊 Growth Tactics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ What Failed:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Paid ads (burned $200, got 2 signups).&lt;/li&gt;
&lt;li&gt;Cold emails (0% response).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ What Worked:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. SEO Long-tail Keywords&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"how to download youtube subtitles for free"&lt;/li&gt;
&lt;li&gt;Traffic: 60% of my total visitors come from organic search.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Reddit (The "Careful" approach)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Answering genuine questions in &lt;code&gt;r/SaaS&lt;/code&gt; and &lt;code&gt;r/Productivity&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warning:&lt;/strong&gt; Don't be spammy. You will get banned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Content Marketing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrote a 5,000-word "Ultimate Guide to YouTube Subtitles".&lt;/li&gt;
&lt;li&gt;It ranks #3 on Google for specific queries. Traffic: 25%.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💸 Revenue Breakdown (Month 6)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Revenue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Individual users&lt;/td&gt;
&lt;td&gt;$320&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API subscriptions&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ads (AdSense)&lt;/td&gt;
&lt;td&gt;$80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$500/mo&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Net profit: ~$435/mo.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Talk to users FIRST:&lt;/strong&gt; Would've discovered the AI angle in Week 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start with paid from day 1:&lt;/strong&gt; Free users give feedback, paid users give validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on ONE channel:&lt;/strong&gt; Don't spread yourself thin across Twitter, LinkedIn, and Reddit. Master one first.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  📈 Key Metrics
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MRR:&lt;/strong&gt; $500&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Churn:&lt;/strong&gt; 15% (Working to improve this)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CAC:&lt;/strong&gt; $12 (mostly organic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LTV:&lt;/strong&gt; $35 (Average user stays 5 months)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🤝 Want to Follow Along?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Check out the tool:&lt;/strong&gt; &lt;a href="https://ytvidhub.com" rel="noopener noreferrer"&gt;ytvidhub.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Questions for you:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What's your biggest challenge with side projects right now?&lt;/li&gt;
&lt;li&gt;Would you pay for a YouTube AI tool?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Drop your thoughts below! 👇&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. If you're building something, ship it. Imperfect action &amp;gt; perfect inaction.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>indiehacke</category>
      <category>showdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I Built a Bulk YouTube Subtitle Extractor as a Solo Dev — Here's My Tech Stack and What I Learned</title>
      <dc:creator>Nuco Z</dc:creator>
      <pubDate>Fri, 27 Feb 2026 03:11:58 +0000</pubDate>
      <link>https://dev.to/nuco_z_270906fb0e460592db/i-built-a-bulk-youtube-subtitle-extractor-as-a-solo-dev-heres-my-tech-stack-and-what-i-learned-21m6</link>
      <guid>https://dev.to/nuco_z_270906fb0e460592db/i-built-a-bulk-youtube-subtitle-extractor-as-a-solo-dev-heres-my-tech-stack-and-what-i-learned-21m6</guid>
      <description>&lt;p&gt;Every developer has that one side project that starts as a weekend hack and turns into something real. For me, it was a tool to bulk-extract YouTube subtitles.&lt;/p&gt;

&lt;p&gt;It started with a simple frustration: I needed transcripts from an entire YouTube playlist for a research project. YouTube lets you copy transcripts one video at a time. I had 200 videos. That wasn't going to work.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://ytvidhub.com/" rel="noopener noreferrer"&gt;YTVidHub&lt;/a&gt; — a web app that extracts subtitles from YouTube videos, playlists, and entire channels in bulk. Paste a URL, pick your format (SRT, VTT, or TXT), and download everything as a ZIP.&lt;/p&gt;

&lt;p&gt;Here's what the journey looked like from a technical perspective.&lt;/p&gt;




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

&lt;p&gt;I wanted something that could ship fast, handle server-side rendering for SEO, and scale without DevOps headaches. Here's what I landed on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; Next.js 16 (App Router)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling:&lt;/strong&gt; Tailwind CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL + Prisma ORM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth:&lt;/strong&gt; JWT-based (custom, no third-party provider)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Features:&lt;/strong&gt; DeepSeek API for video summarization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments:&lt;/strong&gt; Stripe&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting:&lt;/strong&gt; Vercel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;i18n:&lt;/strong&gt; next-intl (4 languages)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Next.js?
&lt;/h3&gt;

&lt;p&gt;Two reasons: SEO and developer experience.&lt;/p&gt;

&lt;p&gt;A subtitle downloader tool lives and dies by organic search traffic. Server-side rendering was non-negotiable. Next.js App Router gave me that plus React Server Components, which meant I could fetch data on the server without shipping unnecessary JavaScript to the client.&lt;/p&gt;

&lt;p&gt;The file-based routing also made it easy to create dozens of SEO landing pages without a complex routing setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not use a third-party auth provider?
&lt;/h3&gt;

&lt;p&gt;Cost. As a solo developer bootstrapping with zero funding, every dollar matters. Auth0 and Clerk are great, but their free tiers have limits that would bite me as I scale. A simple JWT + Google OAuth flow took me a day to implement and costs nothing to run.&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;// Simplified auth flow&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&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;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;email&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;email&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;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is it as feature-rich as Auth0? No. Does it work perfectly for my use case? Yes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hard Part: Subtitle Extraction at Scale
&lt;/h2&gt;

&lt;p&gt;The core technical challenge wasn't building the UI — it was reliably extracting subtitles from YouTube at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 1: YouTube doesn't want you scraping subtitles
&lt;/h3&gt;

&lt;p&gt;YouTube's official Data API doesn't provide subtitle content. You can get video metadata, but not the actual transcript text. The &lt;code&gt;timedtext&lt;/code&gt; endpoint that serves subtitles to the YouTube player is undocumented and changes without notice.&lt;/p&gt;

&lt;p&gt;My approach: I reverse-engineered the subtitle delivery mechanism and built a resilient fetcher that handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple subtitle tracks per video (manual + auto-generated)&lt;/li&gt;
&lt;li&gt;Language detection and selection&lt;/li&gt;
&lt;li&gt;Rate limiting with exponential backoff&lt;/li&gt;
&lt;li&gt;Graceful fallbacks when subtitles aren't available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight was that YouTube serves subtitles as XML, which needs to be parsed and converted to standard formats:&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;// YouTube serves subtitles as XML like this:&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;text start="1.23" dur="4.5"&amp;gt;Hello world&amp;lt;/text&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;xmlToSrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseXmlEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xml&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;entries&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;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;formatTimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&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;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;formatTimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entry&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="s2"&gt;\n`&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="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;
  
  
  Challenge 2: Playlist expansion
&lt;/h3&gt;

&lt;p&gt;A YouTube playlist URL doesn't give you all the video IDs upfront. You need to paginate through the playlist API, which returns 50 items at a time. For a channel with 2,000+ videos, that's 40+ API calls just to get the video list.&lt;/p&gt;

&lt;p&gt;I built a streaming pipeline that starts checking subtitles as soon as the first batch of video IDs arrives, rather than waiting for the full list. This makes the UX feel much faster — users see progress immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 3: Bulk downloads without killing the server
&lt;/h3&gt;

&lt;p&gt;When a user requests subtitles for 500 videos, you can't process them all synchronously. I implemented a queue-based system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User submits playlist URL&lt;/li&gt;
&lt;li&gt;Server expands the playlist and returns video list&lt;/li&gt;
&lt;li&gt;Client requests subtitles in batches of 10&lt;/li&gt;
&lt;li&gt;Each batch is processed in parallel on the server&lt;/li&gt;
&lt;li&gt;Results are streamed back to the client&lt;/li&gt;
&lt;li&gt;Final ZIP is assembled client-side&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This keeps server memory usage flat regardless of playlist size.&lt;/p&gt;




&lt;h2&gt;
  
  
  SEO: The Make-or-Break for Tool Sites
&lt;/h2&gt;

&lt;p&gt;For a free tool targeting organic traffic, SEO isn't a nice-to-have — it's the entire growth strategy. Here's what I did:&lt;/p&gt;

&lt;h3&gt;
  
  
  Programmatic SEO pages
&lt;/h3&gt;

&lt;p&gt;I created dedicated landing pages for different search intents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/youtube-subtitle-downloader&lt;/code&gt; — targets the generic search&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/bulk-youtube-subtitle-downloader&lt;/code&gt; — targets the bulk/power user search&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/what-is-an-srt-file&lt;/code&gt; — targets informational queries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/guide/*&lt;/code&gt; — long-form content for long-tail keywords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each page has unique content, structured data (JSON-LD), and proper meta tags. Next.js makes this straightforward with &lt;code&gt;generateMetadata&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;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;generateMetadata&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&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;params&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;t&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;getTranslations&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;alternates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;buildCanonicalUrl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;buildCanonicalUrl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pathname&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;buildCanonicalUrl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pathname&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="c1"&gt;// ...&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Internationalization for traffic
&lt;/h3&gt;

&lt;p&gt;Adding support for Spanish, German, and Korean multiplied my addressable search volume by ~3x with relatively little effort. &lt;code&gt;next-intl&lt;/code&gt; handles the routing and message loading, and I used AI to help with translations (then had native speakers review them).&lt;/p&gt;

&lt;h3&gt;
  
  
  What I got wrong
&lt;/h3&gt;

&lt;p&gt;I initially created too many pages targeting similar keywords. Google got confused about which page to rank, and none of them ranked well. This is called "keyword cannibalization" — a lesson I learned the hard way. I'm now consolidating pages and using 301 redirects to focus authority.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monetization: The Credits Model
&lt;/h2&gt;

&lt;p&gt;I went with a credits-based system instead of a traditional subscription:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free tier:&lt;/strong&gt; 5 credits per day (1 credit = 1 video subtitle download)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily reward:&lt;/strong&gt; +3 bonus credits for returning daily&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paid plans:&lt;/strong&gt; Credit packs via Stripe for heavy users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why credits instead of subscriptions? Two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lower barrier to entry.&lt;/strong&gt; Users can try the tool without committing to a monthly fee. Most people only need subtitles occasionally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Natural upsell.&lt;/strong&gt; When someone hits their limit mid-workflow, the friction of buying credits is much lower than subscribing. They're already invested in the task.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stripe integration with Next.js was surprisingly smooth:&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;session&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;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&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;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;priceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;success_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/pricing?success=true`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cancel_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/pricing?canceled=true`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;credits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;creditAmount&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The webhook handles credit allocation after payment confirmation. Simple, reliable, no subscription management complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding AI: The Unexpected Differentiator
&lt;/h2&gt;

&lt;p&gt;The original product was just a subtitle downloader. But I noticed users were downloading transcripts to feed them into ChatGPT for summaries. So I built it in.&lt;/p&gt;

&lt;p&gt;Now users can paste a YouTube URL and get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AI-generated summary of the video&lt;/li&gt;
&lt;li&gt;Key points extraction&lt;/li&gt;
&lt;li&gt;A mind map of the content structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used DeepSeek's API for this — it's significantly cheaper than OpenAI for long-context tasks, and the quality is excellent for summarization. The cost per summary is roughly $0.002, which makes it viable even on the free tier.&lt;/p&gt;

&lt;p&gt;This feature turned out to be the biggest differentiator. There are dozens of YouTube subtitle downloaders, but very few that combine bulk extraction with AI analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned After 3 Months
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What worked
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solving my own problem.&lt;/strong&gt; I built this because I needed it. That meant I understood the user deeply from day one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js + Vercel.&lt;/strong&gt; Zero DevOps overhead. I deploy with &lt;code&gt;git push&lt;/code&gt; and never think about servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credits model.&lt;/strong&gt; Lower friction than subscriptions for a tool that people use sporadically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internationalization early.&lt;/strong&gt; Adding i18n from the start was much easier than retrofitting it later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What I'd do differently
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with fewer pages.&lt;/strong&gt; I created 30+ SEO pages before validating which keywords actually had traffic. Should have started with 3-5 and expanded based on data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build in public sooner.&lt;/strong&gt; I spent 2 months building in silence. Sharing progress on Twitter and dev communities from week 1 would have built an audience before launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on backlinks from day one.&lt;/strong&gt; On-page SEO is necessary but not sufficient. Without quality backlinks, Google won't rank you no matter how good your content is.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Numbers (honest)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monthly visitors:&lt;/strong&gt; ~600 (growing slowly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paying users:&lt;/strong&gt; A handful&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revenue:&lt;/strong&gt; Enough for coffee, not rent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time invested:&lt;/strong&gt; ~400 hours over 3 months&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Is it a success? Not yet. But it's a real product that real people use, and the foundation is solid. The growth curve for SEO-driven tools is slow at first and compounds over time. I'm playing the long game.&lt;/p&gt;




&lt;h2&gt;
  
  
  If You're Building a Similar Tool
&lt;/h2&gt;

&lt;p&gt;A few tactical tips:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use Next.js App Router&lt;/strong&gt; if SEO matters. The metadata API and server components are a huge advantage over client-side frameworks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't over-engineer auth.&lt;/strong&gt; JWT + OAuth is enough for most indie projects. You can always migrate to a provider later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ship the MVP in 2 weeks, then iterate.&lt;/strong&gt; My biggest regret is spending too long on features nobody asked for.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Track everything from day one.&lt;/strong&gt; Google Analytics + Search Console + Microsoft Clarity. You can't improve what you can't measure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write content that helps people.&lt;/strong&gt; The best SEO strategy is genuinely useful content. Google is surprisingly good at detecting fluff.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;If you're working on a side project or building in public, I'd love to connect. Drop a comment about what you're building — I read every one.&lt;/p&gt;

&lt;p&gt;You can check out the tool at &lt;a href="https://ytvidhub.com/" rel="noopener noreferrer"&gt;ytvidhub.com&lt;/a&gt; if you ever need YouTube subtitles in bulk.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
