<?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: zhihu wu</title>
    <description>The latest articles on DEV Community by zhihu wu (@zhihu_wu_dea1d82af01a04d7).</description>
    <link>https://dev.to/zhihu_wu_dea1d82af01a04d7</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%2F3921997%2Fa775d61f-ed57-461b-ac46-ed108350189e.png</url>
      <title>DEV Community: zhihu wu</title>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zhihu_wu_dea1d82af01a04d7"/>
    <language>en</language>
    <item>
      <title>How to Read a Diff Like a Developer (and Catch Bugs Before They Merge)</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Wed, 19 Aug 2026 13:17:39 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/how-to-read-a-diff-like-a-developer-and-catch-bugs-before-they-merge-5d05</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/how-to-read-a-diff-like-a-developer-and-catch-bugs-before-they-merge-5d05</guid>
      <description>&lt;p&gt;We all look at diffs every day — pull request reviews, &lt;code&gt;git diff&lt;/code&gt; output, merge conflict resolutions. But most of us only skim for red and green lines. Learning to read a diff structurally turns code review from a chore into the cheapest bug-finding tool you have.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a diff actually is
&lt;/h2&gt;

&lt;p&gt;A diff is a line-by-line comparison between two versions of a file. The concept comes from the classic Unix &lt;code&gt;diff&lt;/code&gt; utility, and it's the foundation of every version control system. Behind the scenes, tools compute the longest common subsequence (LCS) of the two texts: lines that appear in both versions in the same order are "unchanged", and everything else is either added or removed. Understanding this helps you see why a diff looks the way it does — a big block of red followed by a big block of green is usually one rewritten chunk, not a deletion plus an unrelated addition.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical reading order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the boundaries first.&lt;/strong&gt; Where do the changed hunks start and end? A change touching the first few lines often shifts indentation or imports — two different concerns that Git shows as a single hunk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look for structural changes before logic.&lt;/strong&gt; A changed function signature, an extra parameter, a renamed variable — these explain most "why did my code break?" moments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for whitespace-only noise.&lt;/strong&gt; Trailing spaces, line endings, and re-indentation can bury a real change among hundreds of fake ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the removed lines carefully.&lt;/strong&gt; The deleted line is usually where the bug actually was — it's often the line someone's logic still depends on.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When you don't have git history
&lt;/h2&gt;

&lt;p&gt;Not every comparison involves Git. I regularly compare two API responses to debug a payload difference, two CSV dumps to find the row that changed, or a config file (nginx.conf, .env, Docker Compose) before and after an edit. For those quick checks, I use the &lt;a href="https://codetoolbox.pro/tools/diff-checker.html" rel="noopener noreferrer"&gt;CodeToolbox Diff Checker&lt;/a&gt; — paste two versions, and it highlights added lines in green and removed lines in red, with everything processed locally in your browser (nothing gets uploaded). It has saved me from exporting files and squinting at them side by side more times than I can count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Diffs are the universal language of code change. The faster you can read one, the faster you can review a pull request, resolve a conflict, or spot the single line that broke the build.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>JSON vs XML in 2026: Why JSON Won (and Where XML Still Matters)</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Sun, 16 Aug 2026 13:10:58 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/json-vs-xml-in-2026-why-json-won-and-where-xml-still-matters-37fb</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/json-vs-xml-in-2026-why-json-won-and-where-xml-still-matters-37fb</guid>
      <description>&lt;p&gt;In the early 2000s, if you wanted two systems to talk to each other, XML was the answer. Today, JSON is the default for almost every web API. How did that happen, and does XML still have a place?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why JSON won&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JSON's biggest advantage is that it maps directly onto the data structures programming languages already use. An object is a dictionary, an array is a list, a string is a string. When your API client is JavaScript running in a browser, JSON isn't just convenient, it's native: &lt;code&gt;JSON.parse()&lt;/code&gt; and &lt;code&gt;JSON.stringify()&lt;/code&gt; are built in.&lt;/p&gt;

&lt;p&gt;It's also much lighter. Compare a typical XML document with its JSON equivalent and the JSON version is often half the size, because there are no opening and closing tags repeated for every field. That means less bandwidth, faster parsing, and happier mobile users.&lt;/p&gt;

&lt;p&gt;And it's genuinely human-readable in a way XML struggles to match. Compact JSON is still easy to skim; the equivalent XML is dominated by tag noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where XML still matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;None of this means XML is dead. It remains the foundation of several ecosystems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SVG&lt;/strong&gt; is XML, so every icon you ship is technically XML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SOAP-based enterprise APIs&lt;/strong&gt; are still running inside banks, airlines, and government systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Office document formats&lt;/strong&gt; (DOCX, XLSX) are zipped XML files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration files&lt;/strong&gt; like Maven's &lt;code&gt;pom.xml&lt;/code&gt; or Android layouts lean on XML's strictness.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;XML's real strength is namespaces, schemas (XSD), and mixed content, where text is interleaved with child elements. JSON has no direct answer for those. If your document needs attributes and structured validation, XML is a legitimate choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The practical takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For new APIs, choose JSON. It's simpler, faster, and the whole ecosystem, from OpenAPI to JSON Schema to every language's standard library, has converged on it. For documents with complex validation or long-term archival, XML still earns its keep.&lt;/p&gt;

&lt;p&gt;One habit that saves me time daily: after generating API responses, I run them through a formatter to catch syntax errors before debugging code that isn't the problem. &lt;a href="https://codetoolbox.pro/tools/json-formatter.html" rel="noopener noreferrer"&gt;JSON Formatter&lt;/a&gt; is my go-to, it validates, pretty-prints, and works entirely in the browser, so nothing sensitive ever leaves my machine.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Base64 Data URIs: When They Help and When They Hurt</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 13:14:38 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/base64-data-uris-when-they-help-and-when-they-hurt-398d</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/base64-data-uris-when-they-help-and-when-they-hurt-398d</guid>
      <description>&lt;p&gt;If you've ever pasted a small image straight into your HTML, you've used a Base64 data URI: &lt;code&gt;data:image/png;base64,iVBORw0KGgo...&lt;/code&gt;. The browser decodes it as if it were a file — no extra HTTP request, no separate asset to deploy. It's a genuinely useful trick, but it's also easy to overuse. Here's a practical breakdown of when data URIs help, when they hurt, and how to spot the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The math that matters: +33%
&lt;/h2&gt;

&lt;p&gt;Base64 encodes 3 bytes of input into 4 text characters. That means a data URI is always about &lt;strong&gt;33% larger&lt;/strong&gt; than the original file. A 300 KB PNG becomes a ~400 KB string sitting inline in your HTML. On a slow mobile connection, that's real bytes the browser has to download before it can render anything above the fold.&lt;/p&gt;

&lt;p&gt;So the first rule is: &lt;strong&gt;the bigger the asset, the worse the trade-off.&lt;/strong&gt; Data URIs are great for things measured in kilobytes — a 1 KB logo, a 2 KB SVG icon, a few emoji. They're terrible for hero images and product photos.&lt;/p&gt;

&lt;h2&gt;
  
  
  When data URIs genuinely help
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Email signatures&lt;/strong&gt; — the image is embedded in the message body, so it renders even when the recipient's mail client blocks remote images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small icons and SVG backgrounds&lt;/strong&gt; — no extra round trip, and SVGs scale at any resolution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-file demos and prototypes&lt;/strong&gt; — one HTML file with everything inline is trivial to share, attach, or screenshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline contexts&lt;/strong&gt; — anything embedded travels with the document.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When they hurt
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Any image over ~10-20 KB&lt;/strong&gt; — the 33% overhead plus the base64 decode cost rarely beats a regular request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP/2+ sites&lt;/strong&gt; — modern browsers multiplex requests cheaply; one more small request is not the penalty it was in 2012.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images reused on multiple pages&lt;/strong&gt; — each page re-downloads the same encoded string. A cached file is fetched once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching and CDNs&lt;/strong&gt; — you can't give a data URI cache headers, vary it, or serve it from a CDN edge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The quick check
&lt;/h2&gt;

&lt;p&gt;Before inlining, ask: &lt;em&gt;would this file survive as a separate request?&lt;/em&gt; If it's tiny, static, and page-specific — inline it. If it's large, shared, or likely to change — keep it as a real file.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; When you do need Base64 — for a data URI, a JWT payload, or an API response — a free online &lt;a href="https://codetoolbox.pro/tools/base64.html" rel="noopener noreferrer"&gt;Base64 encoder/decoder&lt;/a&gt; makes it a two-second job: paste, encode, copy. No installs, and everything happens in the browser.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Password Entropy, Explained: Why Length Beats Complexity</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Tue, 11 Aug 2026 13:14:58 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/password-entropy-explained-why-length-beats-complexity-4djm</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/password-entropy-explained-why-length-beats-complexity-4djm</guid>
      <description>&lt;p&gt;Some passwords look strong and aren't. &lt;code&gt;Tr0ub4dor&amp;amp;3&lt;/code&gt; has 11 characters, an uppercase letter, a digit, and a symbol — yet crackers love it. Meanwhile &lt;code&gt;correct horse battery staple&lt;/code&gt; is 28 characters of lowercase words and is vastly harder to guess. The difference is &lt;strong&gt;entropy&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is password entropy?
&lt;/h2&gt;

&lt;p&gt;Entropy measures unpredictability, in bits. Every bit doubles the number of possible passwords an attacker must try. The formula is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;entropy = length × log₂(charset size)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lowercase letters (26): 4.7 bits per character&lt;/li&gt;
&lt;li&gt;+ Uppercase (52): 5.7 bits&lt;/li&gt;
&lt;li&gt;+ Digits (62): 6.0 bits&lt;/li&gt;
&lt;li&gt;+ Symbols (~95): 6.6 bits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The charset matters — but only a little. Going from lowercase-only to full symbols buys you under 2 extra bits per character. Adding &lt;strong&gt;one character&lt;/strong&gt; to a lowercase-only password buys you 4.7 bits. That's the whole argument in one sentence: &lt;strong&gt;length beats complexity, roughly two and a half times over.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's do the math
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Tr0ub4dor&amp;amp;3&lt;/code&gt; (11 chars, 4 charsets): 11 × 6.6 ≈ 73 bits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;correcthorsebatterystaple&lt;/code&gt; (25 chars, lowercase): 25 × 4.7 ≈ 118 bits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lowercase passphrase has more than 2⁴⁵ times the combinations. This is why modern guidelines (NIST SP 800-63B included) emphasize length over forced complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use 16+ characters.&lt;/strong&gt; At 16 random characters you're past 90 bits — beyond practical brute force.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let a generator do the work.&lt;/strong&gt; Humans pick &lt;code&gt;Password1!&lt;/code&gt;; CSPRNGs don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique per site.&lt;/strong&gt; A 100-bit password is worthless if it's reused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respect site limits.&lt;/strong&gt; If a site caps length or bans symbols, longer lowercase-only is still strong — just don't shrink a good password to 8 characters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I generate all my passwords with a local tool: the &lt;a href="https://codetoolbox.pro/tools/password-generator.html" rel="noopener noreferrer"&gt;CodeToolbox Password Generator&lt;/a&gt; runs entirely in the browser via &lt;code&gt;crypto.getRandomValues()&lt;/code&gt;, so nothing ever leaves my machine. No signup, no upload — just generate and drop the result into your password manager.&lt;/p&gt;

&lt;p&gt;Length beats complexity. Always.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>15-Year vs 30-Year Mortgage: Do the Math Before You Sign</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Sun, 09 Aug 2026 13:12:06 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/15-year-vs-30-year-mortgage-do-the-math-before-you-sign-4kg1</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/15-year-vs-30-year-mortgage-do-the-math-before-you-sign-4kg1</guid>
      <description>&lt;p&gt;When developers talk about buying a home, the conversation always hits the same fork: 15-year or 30-year mortgage? The 30-year's pitch is a lower monthly payment. The 15-year's pitch is dramatically less interest. Both are true — but the gap is much bigger than most people expect, and it deserves real math instead of a gut feeling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the numbers
&lt;/h2&gt;

&lt;p&gt;Take a $250,000 loan at 6.5%:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;30 years:&lt;/strong&gt; about $1,580/month and roughly $318,900 in total interest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15 years:&lt;/strong&gt; about $2,178/month and roughly $142,000 in total interest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 15-year payment is ~$600/month higher, but it eliminates ~$177,000 in interest — more than half the interest bill. That's not pocket change; that's years of tuition, a serious head start on retirement, or a lot of freedom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the gap is so wide
&lt;/h2&gt;

&lt;p&gt;Amortized loans front-load interest. In year one of a 30-year mortgage at 6.5%, the vast majority of each payment is interest. Over three decades, interest keeps accruing on a large balance. A 15-year loan attacks both levers at once: fewer years of accrual and, typically, a lower rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The counterargument: opportunity cost
&lt;/h2&gt;

&lt;p&gt;A 15-year term isn't automatically correct. The extra $600/month could go into index funds instead. If your expected investment returns comfortably beat your mortgage rate, the 30-year can win on paper — but only if you actually invest the difference. Most people don't, which is why the 15-year is effectively a forced-savings machine with a guaranteed 6.5% return.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple framework
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Can you handle the 15-year payment without stretching? If yes, the interest savings usually win.&lt;/li&gt;
&lt;li&gt;Would the difference actually be invested? If it would evaporate into spending, the 15-year wins by default.&lt;/li&gt;
&lt;li&gt;What's the rate gap? 15-year rates run lower; a 0.5-1% difference makes the shorter term even more compelling.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Do the math on your own loan
&lt;/h2&gt;

&lt;p&gt;Every loan is different — your rate, amount, and timeline change the answer. Whenever I'm comparing terms, I keep a free loan calculator open in a tab; it shows the monthly payment, total interest, and a full amortization schedule, so I can see exactly when the interest share of each payment drops below half. Five minutes with real numbers beats a year of opinions.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The 70% Rule: Finding the Sweet Spot for Web Image Compression</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Thu, 06 Aug 2026 13:11:27 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/the-70-rule-finding-the-sweet-spot-for-web-image-compression-40b3</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/the-70-rule-finding-the-sweet-spot-for-web-image-compression-40b3</guid>
      <description>&lt;p&gt;When I audit page speed for a client, images are the culprit more often than JavaScript, fonts, or render-blocking CSS. A 2MB hero photo will undo months of performance work. But the fix isn't as simple as "compress everything to the max" — crank quality too low and you ship blurry images that make your site look broken.&lt;/p&gt;

&lt;p&gt;Here's the mental model I use to get 60-80% file size savings without visible damage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality follows a curve, not a line
&lt;/h2&gt;

&lt;p&gt;The relationship between the JPEG/WebP quality slider and file size is nonlinear. Going from 100 to 90 barely shrinks anything, while 90 to 70 cuts files roughly in half. The visual difference between 90 and 70, meanwhile, is usually invisible on screen. Below 50, you hit the cliff: file size keeps dropping but artifacts (blocking, ringing around text) become obvious fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 70% rule
&lt;/h2&gt;

&lt;p&gt;As a starting point, compress at quality 70-80. It's the zone where most photos look identical to the original on any normal display but weigh a fraction of the source. Use 85 for hero images and portfolios; drop to 60 for thumbnails and icons. Then adjust per image — different images have different tolerances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format beats quality
&lt;/h2&gt;

&lt;p&gt;Re-encoding a JPEG at quality 75 to WebP at quality 75 typically saves another 25-34% for free. Browsers have supported WebP for years; there's no reason not to use it for photographic content. PNG is different: it's lossless, so no quality slider can make it dramatically smaller — convert screenshots and logos to WebP instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find your actual breakpoint
&lt;/h2&gt;

&lt;p&gt;Don't guess. Compress the same image at several quality levels and compare side by side. The moment you notice degradation, step back up 5%. That number is your image's personal sweet spot — it varies by subject (a gradient sky falls apart before a busy texture does).&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow I use
&lt;/h2&gt;

&lt;p&gt;Most compression tools upload your image to a server, which is slow and a privacy question mark for client assets. I use a browser-based compressor that does everything locally — no upload, no signup, no size caps. The one I keep bookmarked is the &lt;a href="https://codetoolbox.pro/tools/image-compressor.html" rel="noopener noreferrer"&gt;CodeToolbox Image Compressor&lt;/a&gt;; its side-by-side preview and live quality slider make finding the breakpoint a 30-second task.&lt;/p&gt;

&lt;p&gt;Remember: file size is a design constraint like any other. Know your quality curve, respect the 70% rule, and always compare before you download.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>HSL vs HEX: Why I Moved My Design Tokens to HSL</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Tue, 04 Aug 2026 13:43:05 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/hsl-vs-hex-why-i-moved-my-design-tokens-to-hsl-3f44</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/hsl-vs-hex-why-i-moved-my-design-tokens-to-hsl-3f44</guid>
      <description>&lt;p&gt;I used to define every color in my CSS as a hex code. &lt;code&gt;#3b82f6&lt;/code&gt; here, &lt;code&gt;#1e40af&lt;/code&gt; there. It worked fine — until the day I needed a hover state, a disabled state, and a dark-mode variant of the same blue. Suddenly I was doing mental math on hex pairs and hoping the result looked right.&lt;/p&gt;

&lt;p&gt;The problem with hex is that it's written for machines, not humans. You can't look at &lt;code&gt;#2563eb&lt;/code&gt; and guess what it looks like 15% darker. HSL fixes that by describing color the way we actually perceive it: a hue on a wheel (0-360 degrees), a saturation percentage, and a lightness percentage.&lt;/p&gt;

&lt;p&gt;Take the blue &lt;code&gt;#2563eb&lt;/code&gt;. In HSL it's &lt;code&gt;hsl(221, 83%, 53%)&lt;/code&gt;. Want a darker shade for a hover state? Drop the lightness: &lt;code&gt;hsl(221, 83%, 43%)&lt;/code&gt;. Want a lighter tint for a page background? Raise it: &lt;code&gt;hsl(221, 83%, 93%)&lt;/code&gt;. No calculators, no hex arithmetic — you move one number and the color does exactly what you expect.&lt;/p&gt;

&lt;p&gt;This becomes even more powerful with CSS custom properties. Instead of scattering hex values across your stylesheet, store the channels once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--brand-h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;221&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--brand-s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;83%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--brand-l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;53%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.btn-primary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--brand-h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--brand-s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--brand-l&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.btn-primary&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--brand-h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--brand-s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--brand-l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;10%&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;Now your entire theme derives from three numbers. Dark mode becomes a single media query that swaps lightness values. Tailwind's shade scale (50 through 950) is essentially lightness variations of one hue — which is why it feels so cohesive out of the box.&lt;/p&gt;

&lt;p&gt;If you're still thinking in hex, try this experiment: pick your brand color, convert it to HSL, and spend a day using the lightness slider instead of hunting for "a slightly darker blue" on color-picker websites. It's a small mental shift, but it makes your color system dramatically easier to maintain — and it's one less tool you need when someone asks for "the same blue but a bit lighter."&lt;/p&gt;

&lt;p&gt;💡 When I need to explore shades or convert between formats, I use the &lt;a href="https://codetoolbox.pro/tools/color-picker.html" rel="noopener noreferrer"&gt;CodeToolbox Color Picker&lt;/a&gt; — it shows HEX, RGB, and HSL side by side with live sliders, so I can watch how changing lightness ripples through all three formats in real time. Everything runs locally in the browser, so the color I'm working on never leaves my machine.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>design</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>3 Markdown Mistakes I Fixed After Previewing My Drafts</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Sat, 01 Aug 2026 13:05:51 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/3-markdown-mistakes-i-fixed-after-previewing-my-drafts-e1h</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/3-markdown-mistakes-i-fixed-after-previewing-my-drafts-e1h</guid>
      <description>&lt;h2&gt;
  
  
  3 Markdown Mistakes I Fixed After Previewing My Drafts
&lt;/h2&gt;

&lt;p&gt;I write in Markdown every day — READMEs, Dev.to posts, GitHub issues, documentation. I thought I knew the syntax cold. Then I started previewing my drafts before publishing and realized I was making the same three mistakes over and over.&lt;/p&gt;

&lt;p&gt;Here's what I caught, and how you can avoid them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Tables That Look Fine in Raw Markdown — But Break on Render
&lt;/h3&gt;

&lt;p&gt;This is the most common one. You write a table like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| Name | Type | Default |
|------|------|---------|
| timeout | number | 30 |
| retries | number | 3 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks perfect. But then you add a fourth column to one row without updating the header separator line — and suddenly GitHub renders a broken mess. The separator line needs &lt;strong&gt;exactly&lt;/strong&gt; the same number of pipes as every other row. Miss one, and the table falls apart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Always preview your tables. A real-time preview catches misalignment instantly — you'll see the render fail while you're still editing, not after you've pushed to main.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Code Blocks That Eat Your Backticks
&lt;/h3&gt;

&lt;p&gt;You want to show a literal backtick inside a code block. Standard Markdown uses triple backticks for code fences, but what if your code contains triple backticks? The renderer gets confused about where the code block ends.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;original&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Markdown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;spec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;says:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\`backticks\`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;code.&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
json&lt;br&gt;
{&lt;br&gt;
  "example": "this is inside a fenced code block"&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
markdown&lt;/p&gt;

&lt;p&gt;In the raw editor, this looks fine. But the renderer sees the first&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;, then the inner&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
json starts a nested fence (or ends the outer one early). The result is half your content vanishing from the rendered output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Use four backticks for the outer fence when your code contains triple backticks. Or better — preview it. You'll see the break immediately.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Numbered Lists That Reset (When You Don't Want Them To)
&lt;/h3&gt;

&lt;p&gt;You write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Install dependencies
2. Configure the environment

Some paragraph in between.

3. Run the app
4. Open localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Markdown sees that paragraph as a list break. Items 3 and 4 start a new list — so they render as "1. Run the app, 2. Open localhost:3000" instead of continuing the numbering. This is correct per the spec, but it's rarely what you intended.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Indent the paragraph with four spaces to keep it inside the list item, or indent the continuation numbers. Or just preview it — the reset will jump out at you.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Lesson: Preview Before You Publish
&lt;/h3&gt;

&lt;p&gt;All three of these mistakes look correct in raw text. That's the trap — Markdown is designed to be readable in source form, so errors are invisible until you render them.&lt;/p&gt;

&lt;p&gt;I now paste every README update, every Dev.to draft, and every documentation change into a &lt;a href="https://codetoolbox.pro/tools/markdown-preview.html" rel="noopener noreferrer"&gt;Markdown preview tool&lt;/a&gt; before pushing. It's a free, client-side tool — nothing leaves your browser — and it catches these formatting bugs in seconds. The split-pane view (edit on the left, preview on the right) makes it obvious when a table misaligns, a code fence breaks, or a list resets unexpectedly.&lt;/p&gt;

&lt;p&gt;If you write Markdown regularly, make previewing a habit. Five seconds of checking beats a pull request comment pointing out your broken table.&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>5 Developer Writing Tasks That Need More Than Just a Word Count</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Fri, 31 Jul 2026 13:08:55 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/5-developer-writing-tasks-that-need-more-than-just-a-word-count-5fn1</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/5-developer-writing-tasks-that-need-more-than-just-a-word-count-5fn1</guid>
      <description>&lt;h2&gt;
  
  
  5 Developer Writing Tasks That Need More Than Just a Word Count
&lt;/h2&gt;

&lt;p&gt;We obsess over clean code, but how often do we sweat over the words around our code? Commit messages, API docs, README files — these are the interfaces between your code and other humans. A word counter gives you more than just a number; it gives you constraints that sharpen your writing.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The 50/72 Rule for Commit Messages
&lt;/h3&gt;

&lt;p&gt;Git convention says: 50 characters for the subject line, 72 for body lines. Why 50? Because &lt;code&gt;git log --oneline&lt;/code&gt; truncates at 50. Why 72? Because &lt;code&gt;git log&lt;/code&gt; indents by 4 and terminals are 80 columns wide. Paste your commit message into a counter and check: is your subject under 50 characters? Are your body lines under 72? This one habit makes your git history readable on any terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Meta Descriptions Under 160 Characters
&lt;/h3&gt;

&lt;p&gt;Google displays roughly 150–160 characters of your meta description. Every character past 155 is wasted. For developers building landing pages, docs sites, or portfolios, this is the difference between a click and a scroll-past. Write your description, paste it in, trim to 155 characters.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. READMEs People Actually Read
&lt;/h3&gt;

&lt;p&gt;The average developer spends 15 seconds scanning a README before deciding whether to dive in. If your word count is over 500 words and your paragraph count is over 8, you're writing a novel. Use the sentence count — if the average exceeds 25 words per sentence, your README feels dense. Break it up.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Error Messages That Help
&lt;/h3&gt;

&lt;p&gt;"An error occurred." — 3 words, zero value. "Failed to connect to database 'users' at localhost:5432 — connection refused." — 12 words, actionable. Good error messages have a sweet spot: 10–25 words, one sentence. Paste them into a counter and ask: is this long enough to be useful but short enough to be scanned?&lt;/p&gt;

&lt;h3&gt;
  
  
  5. API Documentation Chunks
&lt;/h3&gt;

&lt;p&gt;Nobody reads API docs cover-to-cover. They scan for the endpoint they need. Keep parameter descriptions under 30 words — anything longer means you're burying the signal. Paste your draft endpoint docs into a counter and check each parameter description.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Quick check:&lt;/strong&gt; If you're writing any of the above and want instant feedback, &lt;a href="https://codetoolbox.pro/tools/word-counter.html" rel="noopener noreferrer"&gt;CodeToolbox Word Counter&lt;/a&gt; runs in your browser — no uploads, no signup. Just paste, check, and ship.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>URL Encoding Demystified: encodeURIComponent vs encodeURI</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Wed, 29 Jul 2026 13:08:46 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/url-encoding-demystified-encodeuricomponent-vs-encodeuri-12d3</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/url-encoding-demystified-encodeuricomponent-vs-encodeuri-12d3</guid>
      <description>&lt;p&gt;You are debugging a broken API call. The URL looks fine in your browser, but the server returns 400. You stare at the query string: &lt;code&gt;?q=hello world&amp;amp;filter=active&lt;/code&gt;. There is your problem — that space.&lt;/p&gt;

&lt;p&gt;URL encoding is one of those things every developer encounters but few fully understand. Let us fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two JavaScript Functions (and Why Both Exist)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;encodeURI()&lt;/code&gt; is for &lt;strong&gt;entire URLs&lt;/strong&gt;. It preserves characters that have structural meaning: &lt;code&gt;:&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt;. Use it when you have a complete URL and just need to make it safe.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;encodeURIComponent()&lt;/code&gt; is for &lt;strong&gt;individual parameter values&lt;/strong&gt;. It encodes everything except &lt;code&gt;A-Z a-z 0-9 - _ . ! ~ * ' ( )&lt;/code&gt;. The &lt;code&gt;&amp;amp;&lt;/code&gt; in your filter value? Encoded. The &lt;code&gt;=&lt;/code&gt; in your base64 token? Encoded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;encodeURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/search?q=hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// → "https://example.com/search?q=hello%20world"&lt;/span&gt;

&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// → "hello%20world"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule: &lt;code&gt;encodeURI()&lt;/code&gt; for the whole URL, &lt;code&gt;encodeURIComponent()&lt;/code&gt; for each parameter value. Mix them up and you either break the URL structure or leave dangerous characters unencoded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Double-Encoding Trap
&lt;/h2&gt;

&lt;p&gt;If you encode a string that is already encoded, the &lt;code&gt;%&lt;/code&gt; signs get re-encoded to &lt;code&gt;%25&lt;/code&gt;. &lt;code&gt;hello%20world&lt;/code&gt; becomes &lt;code&gt;hello%2520world&lt;/code&gt;. The server decodes once and gets &lt;code&gt;hello%20world&lt;/code&gt; — still encoded. A second decode gives you the original, but most servers only decode once.&lt;/p&gt;

&lt;p&gt;Spot double-encoding by looking for &lt;code&gt;%25&lt;/code&gt; in your output. If you see it, decode first, then re-encode.&lt;/p&gt;

&lt;h2&gt;
  
  
  %20 vs + for Spaces
&lt;/h2&gt;

&lt;p&gt;In query strings, HTML forms use &lt;code&gt;+&lt;/code&gt; for spaces (&lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;). In path segments, use &lt;code&gt;%20&lt;/code&gt; (RFC 3986). &lt;code&gt;encodeURIComponent()&lt;/code&gt; always outputs &lt;code&gt;%20&lt;/code&gt;, which is safe everywhere. If your server expects &lt;code&gt;+&lt;/code&gt;, use &lt;code&gt;%20&lt;/code&gt; anyway — most modern frameworks handle both.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quick Fix
&lt;/h2&gt;

&lt;p&gt;Next time a URL misbehaves, paste it into a decoder, inspect what is actually being sent, and fix the encoding at the source. A free tool like &lt;a href="https://codetoolbox.pro/tools/url-encoder.html" rel="noopener noreferrer"&gt;CodeToolbox URL Encoder&lt;/a&gt; does this in your browser — no server uploads, instant results.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Base64 Decoded: What Actually Happens When You Hit 'Encode'</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Sun, 26 Jul 2026 13:06:07 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/base64-decoded-what-actually-happens-when-you-hit-encode-1h06</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/base64-decoded-what-actually-happens-when-you-hit-encode-1h06</guid>
      <description>&lt;p&gt;Base64 is not encryption — it's encoding. If you paste &lt;code&gt;Hello&lt;/code&gt; into a Base64 encoder and get &lt;code&gt;SGVsbG8=&lt;/code&gt;, no secret key was used. The algorithm just translates binary data into 64 printable ASCII characters so it can travel through text-only channels like email, JSON, and URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Algorithm in Plain English
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Take your input bytes and group them in sets of 3 (24 bits total).&lt;/li&gt;
&lt;li&gt;Split those 24 bits into four 6-bit chunks.&lt;/li&gt;
&lt;li&gt;Each 6-bit value (0-63) maps to one character in the Base64 alphabet: &lt;code&gt;A-Z&lt;/code&gt;, &lt;code&gt;a-z&lt;/code&gt;, &lt;code&gt;0-9&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the input isn't a multiple of 3 bytes, add &lt;code&gt;=&lt;/code&gt; padding to round it out.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For &lt;code&gt;Hello&lt;/code&gt; (5 bytes), the encoder processes bytes 0-2 as one group (producing 4 characters), then bytes 3-4 as a 2-byte group (producing 3 characters + 1 &lt;code&gt;=&lt;/code&gt;). Result: 8 characters — &lt;code&gt;SGVsbG8=&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where You Encounter Base64 Every Day
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data URIs&lt;/strong&gt; — embed images directly in HTML/CSS as &lt;code&gt;data:image/png;base64,iVBOR...&lt;/code&gt;, saving HTTP requests for small icons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWT tokens&lt;/strong&gt; — the payload section of every JSON Web Token is Base64-encoded JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email attachments&lt;/strong&gt; — MIME uses Base64 to send binary files through text-only SMTP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API authentication&lt;/strong&gt; — Basic Auth encodes &lt;code&gt;username:password&lt;/code&gt; as a Base64 string in the &lt;code&gt;Authorization&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline SVGs&lt;/strong&gt; — embed vector graphics in a single HTML file without external dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Gotchas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Standard vs URL-safe.&lt;/strong&gt; Standard Base64 uses &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt;, which break in URLs. URL-safe Base64 replaces them with &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;_&lt;/code&gt; and strips the &lt;code&gt;=&lt;/code&gt; padding. If you are encoding something for a query string, always use the URL-safe variant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Base64 expands data by ~33%.&lt;/strong&gt; Encoding 1 MB of binary produces ~1.33 MB of text. For large payloads, gzip the original data &lt;em&gt;before&lt;/em&gt; Base64-encoding to offset the bloat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is not encryption.&lt;/strong&gt; Anyone can decode Base64 with zero effort. Never use it as a security measure — it provides exactly zero confidentiality.&lt;/p&gt;




&lt;p&gt;Try it yourself: &lt;a href="https://codetoolbox.pro/tools/base64.html" rel="noopener noreferrer"&gt;Base64 Encoder/Decoder&lt;/a&gt; — free, no signup, works entirely in your browser with no server uploads.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Cron Jobs in 2026: Docker, CI/CD, and Why Your Old crontab Tricks Still Work</title>
      <dc:creator>zhihu wu</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:06:59 +0000</pubDate>
      <link>https://dev.to/zhihu_wu_dea1d82af01a04d7/cron-jobs-in-2026-docker-cicd-and-why-your-old-crontab-tricks-still-work-1mnn</link>
      <guid>https://dev.to/zhihu_wu_dea1d82af01a04d7/cron-jobs-in-2026-docker-cicd-and-why-your-old-crontab-tricks-still-work-1mnn</guid>
      <description>&lt;p&gt;Cron has been scheduling Unix jobs since the 1970s, and it is not going anywhere. But the way we run cron jobs has shifted: from bare-metal servers to Docker containers, from crontab files to CI/CD YAML, and from cron.d to Kubernetes CronJobs. Here is what changed — and what has not.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Cron Inside Docker
&lt;/h2&gt;

&lt;p&gt;Docker containers prefer a single foreground process, so running a cron daemon inside a container feels unnatural. Three modern approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Host cron + &lt;code&gt;docker exec&lt;/code&gt;&lt;/strong&gt;: Run cron on the host, and have each entry invoke &lt;code&gt;docker exec container command&lt;/code&gt;. Zero container changes, works today.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;supercronic&lt;/strong&gt;: A drop-in crontab-compatible runner that logs to stdout/stderr. Perfect for containers whose log drivers expect stdout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ofelia&lt;/strong&gt;: A Docker-native scheduler that reads job config from container labels instead of a crontab file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. CI/CD Cron: GitHub Actions &amp;amp; Friends
&lt;/h2&gt;

&lt;p&gt;GitHub Actions, GitLab CI, and Jenkins all support cron-triggered pipelines. The syntax is identical to standard 5-field cron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions — runs every weekday at 9 AM UTC&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;9&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1-5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key difference: CI/CD schedulers use &lt;strong&gt;UTC&lt;/strong&gt; by default, not your local timezone. A &lt;code&gt;0 0 * * *&lt;/code&gt; job that reads "midnight" actually fires at midnight UTC — which may be 8 PM your time. Always verify the timezone offset.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Classic Traps Have Not Changed
&lt;/h2&gt;

&lt;p&gt;Some mistakes transcend the platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Weekday numbering&lt;/strong&gt;: &lt;code&gt;0&lt;/code&gt; is Sunday (POSIX), but Quartz Scheduler and some libraries start at &lt;code&gt;1&lt;/code&gt; for Sunday. Check your platform convention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OR logic&lt;/strong&gt;: When both day-of-month AND day-of-week are set, the job fires when EITHER matches — not both. This catches everyone at least once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal environment&lt;/strong&gt;: Cron runs without your shell PATH, aliases, or environment variables. Use absolute paths and set &lt;code&gt;SHELL=/bin/bash&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Quick tip&lt;/strong&gt;: I use a &lt;a href="https://codetoolbox.pro/tools/cron-generator.html" rel="noopener noreferrer"&gt;free visual cron generator&lt;/a&gt; to build expressions, see human-readable descriptions, and preview the next 5 execution times before pasting into production. Beats memorizing field order at 2 AM.&lt;/p&gt;

&lt;p&gt;What is the worst cron bug you have deployed? Mine was a timezone mismatch that sent customer emails at 3 AM local time instead of 9 AM.&lt;/p&gt;

</description>
      <category>cron</category>
      <category>devops</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
