<?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: Wolfgang Rathgeb</title>
    <description>The latest articles on DEV Community by Wolfgang Rathgeb (@cordlesswool).</description>
    <link>https://dev.to/cordlesswool</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1136343%2F12bb8a06-027e-4d32-9fd2-e9181791d314.png</url>
      <title>DEV Community: Wolfgang Rathgeb</title>
      <link>https://dev.to/cordlesswool</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cordlesswool"/>
    <language>en</language>
    <item>
      <title>How SQLite Replaced PostgreSQL as My First Choice</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Mon, 15 Dec 2025 15:19:21 +0000</pubDate>
      <link>https://dev.to/cordlesswool/how-sqlite-replaced-postgresql-as-my-first-choice-2p8h</link>
      <guid>https://dev.to/cordlesswool/how-sqlite-replaced-postgresql-as-my-first-choice-2p8h</guid>
      <description>&lt;p&gt;MySQL, NoSQL, and back to PostgreSQL – that's my journey through the database world in a nutshell. With the rise of NoSQL databases, systems like PostgreSQL added new features and evolved into NoSQL (not only SQL) databases themselves, becoming the perfect all-in-one solution.&lt;/p&gt;

&lt;p&gt;SQLite? I found it nice enough for Nextcloud at home when at most 3 users were active simultaneously. A nice tool for getting started, but nothing for production. Or so I thought, until a customer convinced me to use SQLite as the database for his software.&lt;/p&gt;

&lt;p&gt;I gave it a shot and refreshed my knowledge: While PostgreSQL and MySQL were trying to compete with NoSQL databases, SQLite was working on features important for production systems: Scaleability, Non-Blocking Writes, Performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Startup That Over-Engineered Too Early
&lt;/h2&gt;

&lt;p&gt;Sometimes we need others to show us the value in something we've overlooked.&lt;/p&gt;

&lt;p&gt;For me, that was said customer who had gotten completely stuck. The existing software was built on Clojure and an obscure database system whose name I can barely remember, and was completely stuck. Simple features took months instead of days. Developers with the right skills were impossible to find, and the user base was still tiny.&lt;/p&gt;

&lt;p&gt;The plan: We migrated to SvelteKit and SQLite. Simple. Fast. No complex infrastructure to manage and tons of developers in the market. Through pair programming, we introduced the team to SvelteKit and got the new system live. Features flowed easily and the system delivered more than enough performance.&lt;/p&gt;

&lt;p&gt;Unfortunately, it was already too late. Follow-up funding failed and too much money had already been burned through the overly complex system.&lt;/p&gt;

&lt;p&gt;The lesson: &lt;strong&gt;Start simple from day one.&lt;/strong&gt; Complexity comes naturally and you have to adapt to it, but complexity to cover eventualities that may never occur is ultimately technical debt too. &lt;strong&gt;The best code is code you don't need.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SQLite Became My Default Choice
&lt;/h2&gt;

&lt;p&gt;For those unfamiliar with SQLite: it's just a file and a library for your preferred programming language. No separate database server. No Docker container. No complex configuration. You add a dependency, point to a file, and you're done.&lt;/p&gt;

&lt;p&gt;This simplicity is SQLite's greatest strength. While everyone else is setting up PostgreSQL instances, configuring connection pools, and managing database servers, you're already building features.&lt;/p&gt;

&lt;p&gt;Since SQLite runs as an embedded library in your application, network latency is completely eliminated. Every query is a local file operation. &lt;strong&gt;Performance is better than you'd think.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Additionally, modern SQLite features like WAL (Write-Ahead Logging) mode handle concurrent reads and writes efficiently – it's not the single-threaded bottleneck many developers assume, and not comparable to direct file access.&lt;/p&gt;

&lt;p&gt;The library used is also a decisive factor, as it serves as the main link and handles all operations. Bun recognized this and is trying to significantly boost performance with its own implementation.&lt;/p&gt;

&lt;p&gt;Other libraries like libsql – which officially represents a compatible fork – take SQLite to a whole different level and even enable globally scaled systems.&lt;/p&gt;

&lt;p&gt;But no matter how good the stability or performance of the library is, one thing remains the deciding factor: SQLite's structure is so simple that migration to another SQL database always remains possible and easy. &lt;strong&gt;SQLite gives you plenty of room to breathe until you reach that point.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLite in Production: Two Real-World Examples
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Shrtn&lt;/strong&gt; is a URL shortener with one simple goal: to be as simple as possible. Start a single Docker container, mount a volume, done. No Redis server to manage, no PostgreSQL to configure. The data model is simple – one table for links, some auth data, that's it. Could I have used Redis? Sure. But then I would have needed a second container, more configuration, more maintenance overhead. SQLite keeps the entire deployment trivial and handles all redirects without issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EmbodiCMS&lt;/strong&gt; is a Git-based CMS where the actual content lives in Git repositories. SQLite stores the metadata – OAuth accounts, project information, article metadata for filtering and search. The database doesn't hold critical content, just supporting data to make everything work. It's a natural architectural fit: Git handles version-controlled content, SQLite handles the few operational data points. The project is in its early stages and sometimes I wonder about the future, but currently SQLite fulfills all requirements and then some.&lt;/p&gt;

&lt;p&gt;Both projects prove the same point: &lt;strong&gt;SQLite works in production systems when you focus on real problems instead of theoretical scaling.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When PostgreSQL Is Actually the Better Choice
&lt;/h2&gt;

&lt;p&gt;Don't get me wrong – PostgreSQL is an excellent database. But most applications don't need a large palette of features.&lt;/p&gt;

&lt;p&gt;If you need full-text search with trigrams, complex JSON queries, or document management similar to MongoDB, PostgreSQL delivers. These features exist for good reasons. Anyone who has had to implement trigram search themselves will appreciate having it available directly in the database.&lt;/p&gt;

&lt;p&gt;But here's the key: &lt;strong&gt;Use SQLite until you have a proven need for these features.&lt;/strong&gt; Most applications never hit SQLite's limits. And when you need to switch, you know exactly which features you're missing and which database system is best for you.&lt;/p&gt;

&lt;p&gt;Start with SQLite. Switch when you have real requirements, not theoretical ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Migration Path When You Outgrow SQLite
&lt;/h2&gt;

&lt;p&gt;Eventually, the time may come when you need to migrate away from SQLite. SQLite's simple, essentials-focused structure makes this migration remarkably straightforward.&lt;/p&gt;

&lt;p&gt;SQLite focuses on standard SQL and remains compatible at this level with other SQL databases like PostgreSQL, MySQL, OracleDB, or MSSQL. Tools like &lt;code&gt;pgloader&lt;/code&gt; make it even easier – no manual dump wrangling required.&lt;/p&gt;

&lt;p&gt;To facilitate migration, you should use SQLite in strict mode from the start. Without it, SQLite doesn't validate against the schema and allows arbitrary values in fields. With proper data validation, this shouldn't be a problem, but the headaches aren't worth skipping this configuration.&lt;/p&gt;

&lt;p&gt;Additionally and in general, ORMs like Drizzle are extremely helpful for transferring structures, indexes, and other configurations to any SQL database system.&lt;/p&gt;

&lt;p&gt;When the actual time for migration arrives, you already have a much clearer picture of the application and aren't guessing anymore, but know exactly what's needed for which feature. You make an informed decision based on real production data, not architectural panic on day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with SQLite. Build your product. Migrate when you have evidence, not assumptions.&lt;/strong&gt; That's how you stay focused on building something people actually want to use.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>programming</category>
    </item>
    <item>
      <title>Reliable Puppeteer Container Setup for PDF Generation</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Tue, 02 Sep 2025 14:02:46 +0000</pubDate>
      <link>https://dev.to/cordlesswool/reliable-puppeteer-container-setup-for-pdf-generation-198h</link>
      <guid>https://dev.to/cordlesswool/reliable-puppeteer-container-setup-for-pdf-generation-198h</guid>
      <description>&lt;p&gt;When developing &lt;a href="https://dropanote.de/en/blog/20250805-dynamic-pdf-generation-puppeteer/" rel="noopener noreferrer"&gt;PDF generation for a warehouse management system&lt;/a&gt;, I tried using the official Puppeteer container to reduce configuration complexity. I had developed everything locally first, and it worked perfectly.&lt;/p&gt;

&lt;p&gt;But when I deployed with the container, all spacing disappeared - margins, padding, everything was compressed. The PDF generation logic still worked, but the layout was completely broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Puppeteer Container Spacing Issues
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funthh3vwh2nqhrcu8dqr.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%2Funthh3vwh2nqhrcu8dqr.png" alt="Puppeteer PDF output compare from differnt Dockerfile" width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I tried different Puppeteer configurations and researched the problem extensively. After multiple research sessions, AI conversations, and attempts with various configurations, nothing fixed the spacing issues.&lt;/p&gt;

&lt;p&gt;Initially, it wasn't clear whether this was a general container problem or something specific to the official Puppeteer container. The simplest way to evaluate this was building my own container with Chromium installed. Most web tutorials were unnecessarily complex, and it turned out that a simple Chromium installation with proper Puppeteer configuration is completely sufficient.&lt;/p&gt;

&lt;p&gt;When I tested this approach, the spacing issues disappeared completely, confirming it was specifically a problem with the official Puppeteer container.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Working Solution
&lt;/h2&gt;

&lt;p&gt;Here's my working Dockerfile that solves the spacing issues. The main part is in the first few lines - the rest is just standard Node.js setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use the official Node.js LTS image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:lts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="c"&gt;# Install Chromium manually&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    chromium &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Multi-stage build for dependencies&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /temp/dev
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json /temp/dev/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /temp/dev &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm ci

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /temp/prod
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json /temp/prod/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /temp/prod &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm ci &lt;span class="nt"&gt;--production&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;prerelease&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=install /temp/dev/node_modules node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;release&lt;/span&gt;
&lt;span class="c"&gt;# Skip Puppeteer's bundled Chromium since we installed our own&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=install /temp/prod/node_modules node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=prerelease /usr/src/app/build .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=prerelease /usr/src/app/package.json .&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=3733&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3733/tcp&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["node", "./index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Puppeteer starts, it won't find its bundled Chrome version, so you need to tell it which browser to use. If you're using different environments with different Chrome installations, you can set this via environment variables. For local installations, setting the &lt;code&gt;executablePath&lt;/code&gt; is not necessary.&lt;/p&gt;

&lt;p&gt;The key configuration change in your Puppeteer code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&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;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/usr/bin/chromium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--no-sandbox&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;Note: &lt;code&gt;--no-sandbox&lt;/code&gt; reduces security when processing untrusted content. For internal applications like warehouse systems, this trade-off is usually acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating Chromium Version
&lt;/h2&gt;

&lt;p&gt;Keep in mind that Docker will use the cache for builds, so Chromium probably won't be updated with each build. This is fine for development because you save build time, but for production you'll want the current version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; your-image-name &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>docker</category>
      <category>pdf</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Choosing the Right Git Branching Strategy for Your Team</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Sat, 16 Aug 2025 21:11:28 +0000</pubDate>
      <link>https://dev.to/cordlesswool/choosing-the-right-git-branching-strategy-for-your-team-3o8o</link>
      <guid>https://dev.to/cordlesswool/choosing-the-right-git-branching-strategy-for-your-team-3o8o</guid>
      <description>&lt;p&gt;Git Flow is a proven framework, but unlike Git itself, it's not a set standard. Just because it's well-documented and has excellent tool support doesn't mean other git branching techniques couldn't improve your performance much better – Git Flow could cost more than you think.&lt;/p&gt;

&lt;p&gt;I've implemented Git Flow in teams multiple times, and in all cases it was an improvement. But when things change, the tools have to change sometimes too.&lt;/p&gt;

&lt;p&gt;The pattern is clear: &lt;strong&gt;teams often choose Git Flow because it sounds professional, not because they analysed whether it solves their actual problems.&lt;/strong&gt; Here's how to choose more intentionally based on my experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR: Quick Decision Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Risk Level&lt;/th&gt;
&lt;th&gt;Small Team (2-4)&lt;/th&gt;
&lt;th&gt;Medium+ Team (4+)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Low Risk Tolerance&lt;/strong&gt;&lt;br&gt;(Poor testing, compliance, can't break production)&lt;/td&gt;
&lt;td&gt;Simplified Git Flow&lt;/td&gt;
&lt;td&gt;Simplified- / Git Flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;High Risk Tolerance&lt;/strong&gt;&lt;br&gt;(Good testing, can handle brief issues)&lt;/td&gt;
&lt;td&gt;Trunk-based / GitHub Flow&lt;/td&gt;
&lt;td&gt;GitHub Flow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Want to understand the reasoning behind these recommendations? Read on for the detailed analysis.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Git Workflow Structure Matters
&lt;/h2&gt;

&lt;p&gt;Your team's structure and working style should drive your git branching strategy choice, not the other way around. Every development team needs some git workflow structure. The key question: which approach actually fits your team's constraints?&lt;/p&gt;

&lt;p&gt;Several factors influence this choice for development teams:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing Maturity&lt;/strong&gt; – Strong automated tests enable faster workflows. Manual testing requires more structured release processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk Tolerance&lt;/strong&gt; – Can your team afford brief production issues, or does any downtime create serious problems? This depends on your testing confidence, but also regulatory requirements, financial impact, and business criticality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment Frequency&lt;/strong&gt; – Daily deployments need different team coordination than scheduled releases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team Structure&lt;/strong&gt; – Your team size, experience levels, and working relationships matter more than you think. Small experienced teams coordinate naturally. Large or mixed-experience teams need formal processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple Version Maintenance&lt;/strong&gt; – Supporting different customer versions simultaneously breaks most standard workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool Constraints&lt;/strong&gt; – GitHub's basic merge capabilities work differently than Bitbucket's Git Flow integration.&lt;/p&gt;

&lt;p&gt;We'll focus on the three most fundamental factors for development teams: &lt;strong&gt;testing maturity, risk tolerance, and team structure.&lt;/strong&gt; We'll examine testing maturity and risk tolerance together because of their similarity for most teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Maturity &amp;amp; Risk Tolerance: The Foundation of Workflow Choice
&lt;/h2&gt;

&lt;p&gt;Testing confidence directly shapes what git workflow risks your team can accept. Development teams with strong automated testing can use faster, simpler workflows. Teams without solid testing need workflow barriers to prevent chaos. The key insight: you can reduce workflow complexity by investing in better testing practices first.&lt;/p&gt;

&lt;p&gt;However, testing quality isn't the only factor. Regulatory requirements, financial impact, and business criticality also constrain your team's choices.&lt;/p&gt;

&lt;p&gt;I've seen this dynamic play out across different projects. At an education platform with insufficient automated tests and poor manual testing, Git Flow couldn't solve our underlying quality problems. While it improved stability, the fundamental issues remained - structure alone wasn't enough when the quality foundation was missing.&lt;/p&gt;

&lt;p&gt;A public sector project showed a different pattern. With no automated tests but solid manual testing processes, Git Flow provided necessary release structure and worked well for organising deployments. However, even this success came with hidden costs. Developers frequently had to context-switch back to fix old code days later when manual testing found issues, destroying team focus and momentum. We improved this by using available testing capacity to test during the development phase rather than only at release time.&lt;/p&gt;

&lt;p&gt;The lesson for development teams: Git Flow can organise existing quality processes, but it cannot replace missing ones. Rather than adding workflow complexity to manage poor feedback loops, invest in better testing practices first. This enables simpler, more efficient workflows long-term.&lt;/p&gt;

&lt;h2&gt;
  
  
  Team Structure: Coordination Complexity Drives Process Needs
&lt;/h2&gt;

&lt;p&gt;Your team's structure determines coordination complexity. This directly affects how much formal process your git branching strategy needs. Team size matters, but experience levels and working relationships matter more.&lt;/p&gt;

&lt;p&gt;At a startup with 2-3 experienced developers who knew each other well, trunk-based development or GitHub Flow worked perfectly (never forget the other factors). But with a monolith codebase and 4+ mixed-experience developers, our team needed formal processes.&lt;/p&gt;

&lt;p&gt;In large development teams, trunk-based approaches generally become problematic. Check the other factors to make the right decision for your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple Version Maintenance: When Git Flow Gets Complex
&lt;/h2&gt;

&lt;p&gt;Git Flow assumes linear progression: develop → release → main → next cycle. But maintaining multiple versions for different customers breaks this model entirely. When your team needs to backport fixes to v1.5, v2.1, and v3.0 simultaneously, Git Flow's dual-branch structure becomes unwieldy.&lt;/p&gt;

&lt;p&gt;At the education platform, we needed to transition away from Git Flow when version maintenance became necessary. But I argued against making the change at that time - the timing was too chaotic with pandemic-related stress to add a workflow change on top of everything else. People need capacity to execute such processes. Even though the Git workflow would have become simpler, the transition itself would have been another major burden. This experience confirmed that workflow changes need stability windows. Better to stabilise first, then evolve when the team has bandwidth for change.&lt;/p&gt;

&lt;p&gt;Most Git Flow tooling doesn't support multi-version scenarios well. Your development team ends up with complex cherry-picking workflows and coordination overhead. This defeats the purpose of having a structured process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For teams maintaining multiple versions:&lt;/strong&gt; Consider branch-per-version strategies instead of trying to force Git Flow into a model it wasn't designed for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git Flow Complexity: Hidden Coordination Overhead
&lt;/h2&gt;

&lt;p&gt;Git Flow looks simple in theory but requires sophisticated tooling for development teams to use properly. Desktop clients like SourceTree or GitKraken support the branching operations fine. But they don't help with pull request workflows that most teams rely on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Git Flow's dual-branch model needs coordinated merging. Hotfixes must reach both main AND develop. Release branches need proper handling. Feature branches require dual-branch coordination. Only Bitbucket has proper integration for this - and even then, your team sometimes has to do manual steps with hotfixes.&lt;/p&gt;

&lt;p&gt;With GitHub or GitLab, development teams end up with manual coordination steps that people forget. This creates merge conflicts and missed updates. The alternative is building CI/CD pipeline automation to handle the missing coordination. But this adds complexity most teams don't expect when choosing Git Flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main     o---------o-------o
          \       /       /
release    \     o-------o
            \   /       /
develop      o-o-------o
              \       /
feature        o-----o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GitHub Flow: Simple and Fast
&lt;/h2&gt;

&lt;p&gt;GitHub Flow works with a single main branch representing production. Development teams create feature branches from main, develop changes, merge back via pull request, and deploy immediately. This eliminates coordination complexity entirely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main     o---------o--------o
          \       / \      /
feature    o-----o   o-o--o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This git branching strategy works well when your team has solid automated testing and can tolerate briefly buggy versions whilst fixes deploy. The fast feedback loop and minimal overhead make it ideal for development teams with good testing discipline and acceptable risk tolerance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The limitation:&lt;/strong&gt; No formal release process for stakeholder review or coordinated deployments. If your team needs testing phases or scheduled releases, GitHub Flow becomes problematic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trunk-Based Development: Minimal Branching
&lt;/h2&gt;

&lt;p&gt;Trunk-based development means your entire team commits directly to main. Only viable for solo developers or very small, highly coordinated teams (2-3 people doing pair programming). Most development teams should start with GitHub Flow instead.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Simplified Git Flow: The Middle Ground
&lt;/h2&gt;

&lt;p&gt;Based on my experience with Git Flow's coordination problems across multiple development teams, I invented this simplified approach. It keeps the release structure whilst eliminating the dual-branch complexity. Your team works directly with main as the integration branch, creating release branches when ready for formal testing and stakeholder review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main        o-----o---o---o---o---o-----o---o
             \   /     \       /   \   /   /
feature       o-o       \     /     o-o   /
                         \   /           /
release-1.0               o-o           /  (tag v1.0, deploy, delete branch)
                             \         /
hotfix-1.0.1                  o-------o  (tag v1.0.1, merge back to main)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The workflow for development teams:&lt;/strong&gt; Branch from main when ready to create a release. Test and review on the release branch. Tag the release branch commit itself. Deploy from that tagged commit, then delete the release branch. For hotfixes, branch from the specific tag you need to fix, then merge back to main.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benefits for your team:&lt;/strong&gt; Formal release process without dual-branch coordination. Works with basic tooling. Eliminates the develop/main synchronisation overhead. Tools like changeset can help manage versioning, tags, and changelogs automatically. You get Git Flow's structure with much simpler coordination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't Fear Changing Your Workflow
&lt;/h2&gt;

&lt;p&gt;The technical part of switching git workflows is trivial for development teams - Git doesn't care about your branch names or merging patterns. The real challenge is organisational: achieving team acceptance, retraining habits, and managing the transition period.&lt;/p&gt;

&lt;p&gt;The most important measure for any development team: does your git branching strategy help your team or constantly fight against you? Some friction is normal - that's the price of having a versioning tool, and it's worth it. But if your team is fighting the workflow more than benefiting from it, it's time to evolve.&lt;/p&gt;

&lt;p&gt;Start somewhere reasonable for your team, learn what works for your specific situation, then adjust based on reality rather than theory. Your development team isn't locked into the first choice forever.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>management</category>
      <category>programming</category>
    </item>
    <item>
      <title>How Type Branding Solved an 'Impossible' TypeScript Problem</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Fri, 08 Aug 2025 14:47:40 +0000</pubDate>
      <link>https://dev.to/cordlesswool/how-type-branding-solved-an-impossible-typescript-problem-4k21</link>
      <guid>https://dev.to/cordlesswool/how-type-branding-solved-an-impossible-typescript-problem-4k21</guid>
      <description>&lt;p&gt;When I started building object transformation pipelines, I had a primary goal: write code that's easy to read and change. Complex, nested transformations slow down development, make debugging painful, and turn simple changes into archaeology expeditions.&lt;/p&gt;

&lt;p&gt;I was tired of seeing patterns like this throughout codebases:&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;// "Organized" version - functions exist, but still verbose&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addAge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&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="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="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calculateAge&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;birthDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addEmailValidation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&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="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="na"&gt;emailValid&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="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addPermissions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&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="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="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getUserPermissions&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;role&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RawUser&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;withAge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;addAge&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;withValidation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;addEmailValidation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withAge&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;withPermissions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;addPermissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withValidation&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;withPermissions&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;Sure, functions exist. But look at all those intermediate variables! Each step needs a meaningful name, and you're constantly tracking variables - reading assignments, remembering what each contains, finding where they're used next, and following the chain of function calls.&lt;/p&gt;

&lt;p&gt;What I wanted was something that reads like a recipe:&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;// Clear, step-by-step transformation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addAge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addEmailValidation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addPermissions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sequential flow. Each step obvious. Easy to add, remove, or reorder transformations. Code that tells you &lt;em&gt;what&lt;/em&gt; it does, not &lt;em&gt;how&lt;/em&gt; it's nested.&lt;/p&gt;

&lt;p&gt;The final piece: write generic transformation functions once, compose them into readable pipelines everywhere with complete type safety.&lt;/p&gt;

&lt;p&gt;TypeScript had other plans.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mysterious Loss of Type Information with TypeScript's Generic Functions
&lt;/h2&gt;

&lt;p&gt;Building a &lt;code&gt;pipe&lt;/code&gt; function in TypeScript is already challenging. Getting the type system to understand what flows through a &lt;code&gt;reduce&lt;/code&gt; operation, inferring correct return types, handling async/sync mixing—that's a whole technical adventure on its own (and probably deserves its own article).&lt;/p&gt;

&lt;p&gt;But I figured I could work around those pipe implementation details. The real problem came when I tried to build the actual transformation functions.&lt;/p&gt;

&lt;p&gt;My approach seemed straightforward:&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;// Write once, use with any object that has the right properties&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addAge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calculateAge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addEmailValidation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&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="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;emailValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Use with different types&lt;/span&gt;
&lt;span class="kd"&gt;const&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;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Max&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;max@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1990-01-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;C123&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@company.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1985-05-15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;premium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addAge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addEmailValidation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vision was simple: generic functions that work with any compatible type, preserving all original properties while adding new ones.&lt;/p&gt;

&lt;p&gt;But TypeScript had different ideas:&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;result1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;processData&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="c1"&gt;// Type: { emailValid: boolean }&lt;/span&gt;
&lt;span class="c1"&gt;// Expected: User &amp;amp; { age: number } &amp;amp; { emailValid: boolean }&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;processData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Type: { emailValid: boolean }&lt;/span&gt;
&lt;span class="c1"&gt;// Expected: Customer &amp;amp; { age: number } &amp;amp; { emailValid: boolean }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript lost track of everything. The original &lt;code&gt;User&lt;/code&gt; type? Gone. The &lt;code&gt;age&lt;/code&gt; property from the first transformation? Overwritten. My &lt;code&gt;Customer&lt;/code&gt; with all its properties? Reduced to a single boolean.&lt;/p&gt;

&lt;p&gt;The functions worked perfectly at runtime—JavaScript executed correctly and all properties were preserved. But TypeScript's type system had no idea what was happening in the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempting TypeScript Generic Type Detection
&lt;/h2&gt;

&lt;p&gt;The problem was clear, but the solution wasn't. How do you tell a type system "this function should merge, not replace" when it has no way to understand that distinction?&lt;/p&gt;

&lt;p&gt;I tried several approaches to bridge this gap:&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;// Attempt 1: Use conditional types to detect generics&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;IsGeneric&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;any&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Doesn't work - TypeScript can't introspect function generics like this&lt;/span&gt;

&lt;span class="c1"&gt;// Attempt 2: Universal generic constraints&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SmartPipe&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt; &lt;span class="nf"&gt;extends &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;F&lt;/span&gt; &lt;span class="nf"&gt;extends &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// TypeScript couldn't make the leap from "might be generic" to "should merge"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even when you try to define a function within a generic type, you run into limitations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This WOULD work at the type level&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AddAge&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// You can use it, but you have to set the generic when you use it:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addAgeForUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AddAge&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calculateAge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birthDate&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 limitation is that you have to concrete the generic type T when you USE the type. You can't have a "non-prepared" type that works dynamically with any compatible input.&lt;/p&gt;

&lt;p&gt;The normal generic function approach works fine individually:&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;addAge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calculateAge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birthDate&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;But T cannot be defined inside the type system in a way that allows the pipe system to automatically infer the relationship between input and output types during composition.&lt;/p&gt;

&lt;p&gt;The fundamental issue kept surfacing: &lt;strong&gt;there was no automatic way to tell the type system "this generic function should merge its result with the input."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's when I remembered the &lt;code&gt;__brand&lt;/code&gt; pattern I'd seen before. If TypeScript couldn't detect the intent automatically, maybe I could mark functions explicitly. The wrapper function became the vehicle for carrying the type information the pipe system needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Type Branding for Automatic Detection
&lt;/h2&gt;

&lt;p&gt;The breakthrough came when I stopped fighting TypeScript's limitations and started working with them. If the type system couldn't automatically detect function intent, what if I made that intent explicit?&lt;/p&gt;

&lt;p&gt;The answer was &lt;strong&gt;branding functions so the pipe system could recognize them automatically&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;enrich&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FI&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;AnyObject&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;fu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;FO&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;GMerge&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* implementation */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;GMerge&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GMerge&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;O&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GMergeFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;O&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;__brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GMerge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// This is the detection key&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same branding approach works for &lt;code&gt;pick()&lt;/code&gt; and &lt;code&gt;omit()&lt;/code&gt; functions too:&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;// GType = GMerge | GPick | GOmit&lt;/span&gt;
&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;GType&lt;/span&gt;  &lt;span class="c1"&gt;// Does this function have any brand?&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;GQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;PrevReturn&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;  &lt;span class="c1"&gt;// Yes: use branded logic&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;               &lt;span class="c1"&gt;// No: use transformation logic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the pipe automatically distinguishes between different function types without you having to think about it:&lt;/p&gt;

&lt;p&gt;When you write:&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;processUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;validateInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// No brand: transforms&lt;/span&gt;
  &lt;span class="nf"&gt;enrich&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addAge&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// GMerge brand: merges&lt;/span&gt;
  &lt;span class="nf"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="c1"&gt;// GPick brand: picks&lt;/span&gt;
  &lt;span class="nf"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tempField&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="c1"&gt;// GOmit brand: omits&lt;/span&gt;
  &lt;span class="nx"&gt;formatForAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// No brand: transforms&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipe automatically applies the correct type behavior for each function based on its brand, without you having to specify anything beyond the wrapper function.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Original Vision: Finally Achieved
&lt;/h2&gt;

&lt;p&gt;Remember what started this whole journey? I wanted to write generic transformation functions once and use them everywhere, preserving all type information through complex pipelines.&lt;/p&gt;

&lt;p&gt;That "impossible" dream? It finally works:&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;// Write once, generic transformation functions&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addTimestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;enrich&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;AnyObject&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addMetadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;enrich&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;AnyObject&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;processed&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="c1"&gt;// Use with any compatible type&lt;/span&gt;
&lt;span class="kd"&gt;const&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;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;john@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;C123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;premium&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@company.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;P456&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Widget&lt;/span&gt;&lt;span class="dl"&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="mf"&gt;29.99&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Same pipeline works with all three types&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processAny&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addTimestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addMetadata&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;processedUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;processAny&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="c1"&gt;// Type: User &amp;amp; { timestamp: Date } &amp;amp; { version: string; processed: boolean }&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processedCustomer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;processAny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Type: Customer &amp;amp; { timestamp: Date } &amp;amp; { version: string; processed: boolean }&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processedProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;processAny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Type: Product &amp;amp; { timestamp: Date } &amp;amp; { version: string; processed: boolean }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every property preserved. Every type correctly inferred. The generic functions work with any object shape while maintaining complete type safety.&lt;/p&gt;

&lt;p&gt;This is the "write once, use everywhere" vision that seemed impossible with TypeScript's limitations - now working perfectly. The &lt;code&gt;GMerge&lt;/code&gt; branding system enables automatic detection while preserving generic context through the pipe.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost and Benefits
&lt;/h2&gt;

&lt;p&gt;Let's be honest about what this solution costs and what you get in return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Costs:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime overhead&lt;/strong&gt; - Each enriched function adds a wrapper call and object spread operation. In performance-critical loops processing thousands of objects per second, this could matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learning curve&lt;/strong&gt; - Your team needs to understand when to use &lt;code&gt;enrich()&lt;/code&gt; vs regular transformation functions. It's another pattern to learn and remember.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can't remove properties&lt;/strong&gt; - &lt;code&gt;enrich()&lt;/code&gt; only adds or modifies properties. To remove properties, you need &lt;code&gt;omit()&lt;/code&gt; functions or regular transformations. (The &lt;a href="https://github.com/CordlessWool/pipe-and-combine" rel="noopener noreferrer"&gt;pipe-and-combine&lt;/a&gt; library (&lt;a href="https://www.npmjs.com/package/pipe-and-combine" rel="noopener noreferrer"&gt;npm&lt;/a&gt;) includes &lt;code&gt;omit()&lt;/code&gt; and &lt;code&gt;pick()&lt;/code&gt; functions that use the same branding system for type-safe property removal.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Function specialization&lt;/strong&gt; - Instead of flexible functions that can add, modify, or remove as needed, you now have specialized function types with specific behaviors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But here's the key insight:&lt;/strong&gt; Most of the complexity happens at compile time, not runtime. All the complex type logic - &lt;code&gt;GMerge&lt;/code&gt;, &lt;code&gt;GQueue&lt;/code&gt;, conditional types, generic inference - gets erased when TypeScript compiles to JavaScript. The actual runtime cost is just wrapper functions and object spreading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Readability - The Primary Win&lt;/strong&gt; - This is the biggest advantage. Pipelines read like business processes:&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;processNewUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;validateUserInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;addAge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;addWelcomeTimestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;assignDefaultPermissions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;formatForDatabase&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;Even non-developers can understand this flow. Compare that to nested function calls or imperative code with intermediate variables. The business logic becomes immediately obvious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reduced documentation needs&lt;/strong&gt; - When code reads like business requirements, you need far less external documentation. Good function names eliminate the need to explain what the process does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Faster development&lt;/strong&gt; - Write generic transformation functions once, use them everywhere with complete type safety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintainability&lt;/strong&gt; - Easy to add, remove, or reorder transformation steps without breaking types.&lt;/p&gt;

&lt;p&gt;For most applications, the development speed gains far outweigh the runtime costs. You're trading microseconds for hours of developer time and significantly more maintainable code.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why I Replaced i18next with Paraglide.js</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Sun, 27 Jul 2025 13:57:00 +0000</pubDate>
      <link>https://dev.to/cordlesswool/why-i-replaced-i18next-with-paraglidejs-22d2</link>
      <guid>https://dev.to/cordlesswool/why-i-replaced-i18next-with-paraglidejs-22d2</guid>
      <description>&lt;p&gt;Often your first choice is not your best one. Investing hours in comparing tools and reading documentation can be annoying, and most i18n libraries look pretty similar anyway. So my decision fell quickly to the popular choice: i18next.&lt;/p&gt;

&lt;p&gt;But as always, I kept my eyes open and discovered Paraglide.js - which approaches i18n very differently from other tools.&lt;/p&gt;

&lt;p&gt;The JavaScript world is evolving toward smarter bundling - away from shipping unused code toward build-time optimization. While websites keep getting bigger and more complex, the tooling has gotten more precise about what actually reaches users.&lt;/p&gt;

&lt;p&gt;Here's why I made the switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move from Runtime to Build Time
&lt;/h2&gt;

&lt;p&gt;i18next follows the traditional i18n approach: handle everything at runtime. Every time a user loads your page, it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loads translation files from the server&lt;/li&gt;
&lt;li&gt;Parses JSON structures&lt;/li&gt;
&lt;li&gt;Handles key lookups and interpolation&lt;/li&gt;
&lt;li&gt;Processes pluralization rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This works fine and has been the standard for years. But modern build tools opened up a different possibility.&lt;/p&gt;

&lt;p&gt;Paraglide.js shifts most of the work to build time. During compilation, it generates individual JavaScript functions for each translation key.&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;// i18next: runtime processing&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="s2"&gt;welcome_user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Paraglide.js: pre-generated function call&lt;/span&gt;
&lt;span class="nf"&gt;welcome_user&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both systems still check the current locale at runtime - users can switch languages. But while i18next also parses translation keys and handles interpolation with every call, Paraglide.js pre-compiles this work into ready-to-use functions.&lt;/p&gt;

&lt;p&gt;If you're using a build tool like Vite, this happens automatically. Your users download the final functions instead of translation processing logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reducing Bundle Size by Over 40KB
&lt;/h2&gt;

&lt;p&gt;40KB might not sound significant - "websites weigh over 1MB nowadays anyway," you might think. But every KB affects Core Web Vitals and mobile performance. Modern build tools have become precise about eliminating unnecessary overhead for good reason.&lt;/p&gt;

&lt;p&gt;40KB just to handle translations. That's enough space for thousands of actual translated strings - probably your entire app's content in multiple languages. Paraglide.js reduces this to ~2KB.&lt;/p&gt;

&lt;p&gt;The difference comes down to philosophy: ship results, not processing engines.&lt;/p&gt;

&lt;p&gt;Migration is mostly find-and-replace work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find: t\(['"`]([^'"`]+)['"`](?:,\s*(\{[^}]+\}))?\)
Replace: $1($2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converts both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;t('welcome_message')&lt;/code&gt; → &lt;code&gt;welcome_message()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;t('welcome_user', { name: 'John' })&lt;/code&gt; → &lt;code&gt;welcome_user({ name: 'John' })&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Don't Care About Translation File Size - Just Shake the Tree
&lt;/h2&gt;

&lt;p&gt;Here's the best part: forget about worrying if your translation files get too big or whether you need to reduce them for load speed - it happens automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (i18next):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// All translations ship to users&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;welcome_message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin_panel_title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Admin Dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;debug_info&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Debug mode active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unused_feature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Feature not implemented&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ... 200+ more translations&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (Paraglide.js):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Only import what you actually use&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;welcome_message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// admin_panel_title, debug_info, unused_feature automatically eliminated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With i18next, users download all translations whether they're used or not. With Paraglide.js, the logic is no longer hidden from build tools - they can finally do what they're designed for: optimizing your code. Your translation files stay complete, but users only get what your code actually imports.&lt;/p&gt;

&lt;p&gt;Bonus: you can reuse comprehensive translation files between projects without worrying about bloat. Keep everything, ship only what's needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript Support as a Bonus
&lt;/h2&gt;

&lt;p&gt;Since Paraglide.js generates TypeScript-compatible code, you get native TypeScript support automatically when you import them directly. Real autocomplete, real parameter checking, real compile-time errors - no type generation needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;welcome_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item_count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;welcome_user&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// Autocomplete suggests 'name' parameter&lt;/span&gt;
&lt;span class="nf"&gt;item_count&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// TypeScript error: 'count' should be number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript can be slow picking up new translations, which becomes noticeable when adding many translations quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Prefer Build Time Over Runtime
&lt;/h2&gt;

&lt;p&gt;This comes down to a fundamental principle of software development: do things once, reuse the result. Why process the same translations the same way hundreds, thousands, or millions of times when you could do it once?&lt;/p&gt;

&lt;p&gt;With i18next, every user on every page load processes translations identically. With Paraglide.js, this work happens once during build - then gets reused by every user forever.&lt;/p&gt;

&lt;p&gt;Even though computing power is cheap nowadays, this philosophy still improves user experience. Why make users' devices do work that could have been done on your build server? Why ship processing logic when you could ship the result?&lt;/p&gt;

&lt;p&gt;It's not just about performance - it's about doing things right. Build-time optimization follows the core principle: write once, run everywhere.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Paraglide.js Setup: Type-Safe i18n Without Framework Lock-in</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Sun, 06 Jul 2025 22:53:00 +0000</pubDate>
      <link>https://dev.to/cordlesswool/paraglidejs-setup-type-safe-i18n-without-framework-lock-in-4mj7</link>
      <guid>https://dev.to/cordlesswool/paraglidejs-setup-type-safe-i18n-without-framework-lock-in-4mj7</guid>
      <description>&lt;p&gt;Most i18n solutions work across frameworks, but they come with tradeoffs. Runtime overhead, complex configuration, or weak TypeScript integration. You get flexibility, but you sacrifice performance or developer experience.&lt;/p&gt;

&lt;p&gt;Paraglide.js is different. Compile-time translation generation means zero runtime overhead, full type safety, and a dead-simple API. No complex configuration, no runtime bundle bloat, no guessing which translation keys exist.&lt;/p&gt;

&lt;p&gt;Version 2.0 introduced a framework-agnostic Vite plugin that automatically triggers builds when translations change, eliminating the manual compilation step required in v1.&lt;/p&gt;

&lt;p&gt;I use Paraglide in production on &lt;a href="https://dropanote.de" rel="noopener noreferrer"&gt;dropanote.de&lt;/a&gt;, built with my own site builder. Here's why it works better than the alternatives, and how to set it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Setup
&lt;/h2&gt;

&lt;p&gt;Start with the init command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @inlang/paraglide-js@latest init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates your project configuration and translation file structure. Then add the Vite plugin to your &lt;code&gt;vite.config.ts&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;paraglideVitePlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@inlang/paraglide-js/vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;paraglideVitePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./project.inlang&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;outdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/lib/paraglide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Translation Files
&lt;/h2&gt;

&lt;p&gt;The init command creates a &lt;code&gt;messages/&lt;/code&gt; directory with your translation files. Each locale gets its own JSON file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;messages/
├── en.json
└── de.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your English translations in &lt;code&gt;messages/en.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome to our site"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav_home"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Home"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav_about"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"About"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;German translations in &lt;code&gt;messages/de.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hallo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Willkommen auf unserer Website"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav_home"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Startseite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav_about"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Über uns"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you build your project, Paraglide generates TypeScript functions for each translation key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;

&lt;p&gt;Start your dev server and the Vite plugin will automatically generate your translation functions:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then import the generated message functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/lib/paraglide/messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use your translations with full type safety:&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;// Simple messages&lt;/span&gt;
&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// "Hello" or "Hallo"&lt;/span&gt;
&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// "Welcome to our site" or "Willkommen auf unserer Website"&lt;/span&gt;

&lt;span class="c1"&gt;// Nested keys&lt;/span&gt;
&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nav_home&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// "Home" or "Startseite"&lt;/span&gt;
&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nav_about&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// "About" or "Über uns"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For language switching, import the locale functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;setLocale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getLocale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/lib/paraglide/runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switch languages programmatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// "Hallo"&lt;/span&gt;

&lt;span class="nf"&gt;setLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// "Hello"&lt;/span&gt;

&lt;span class="c1"&gt;// Check current locale&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getLocale&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// "en"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your editor provides autocomplete for all translation keys, and TypeScript catches typos at compile time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parameters in Translations
&lt;/h2&gt;

&lt;p&gt;Your translations can accept dynamic values using curly brace placeholders:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In your translation files:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"greeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello {name}!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"item_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You have {count} items in your cart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome back, {first_name} {last_name}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In your code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/lib/paraglide/messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&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 Alice!"&lt;/span&gt;
&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item_count&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// "You have 5 items in your cart"&lt;/span&gt;
&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_profile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// "Welcome back, John Doe"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript enforces the required parameters - you'll get compile errors if you forget them or use the wrong names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework Integration
&lt;/h2&gt;

&lt;p&gt;The beauty of Paraglide is that it works identically across frameworks. The same import, the same functions, the same API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/lib/paraglide/messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;setLocale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/lib/paraglide/runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In vanilla JavaScript:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/lib/paraglide/messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;setLocale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/lib/paraglide/runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Language switching&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lang-de&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;updateUI&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateUI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nav-home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nav_home&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In React:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Welcome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In Vue:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In Svelte:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No framework-specific wrappers, no different APIs to learn. The same translation functions work everywhere because they're just JavaScript functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Configuration Options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Language Detection Strategy
&lt;/h3&gt;

&lt;p&gt;Configure how Paraglide determines the user's language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;paraglideVitePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./project.inlang&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/lib/paraglide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cookie&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localStorage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sessionStorage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;baseLocale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;url&lt;/code&gt; - checks the URL path (&lt;code&gt;/de/about&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cookie&lt;/code&gt; - checks for a language cookie&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;header&lt;/code&gt; - checks Accept-Language header (SSR)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;localStorage&lt;/code&gt; - checks browser local storage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sessionStorage&lt;/code&gt; - checks browser session storage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;baseLocale&lt;/code&gt; - falls back to your default language&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paraglide tries these strategies in order until it finds a valid locale. If none match, it uses your &lt;code&gt;baseLocale&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Output Directory
&lt;/h3&gt;

&lt;p&gt;Change where generated files are placed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;paraglideVitePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./project.inlang&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/i18n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Custom location&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then import from your custom path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/i18n/messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Bundle size matters. Every kilobyte of JavaScript affects your page load times, especially on mobile devices.&lt;/p&gt;

&lt;p&gt;Traditional i18n libraries ship their entire runtime to the browser - parsers, formatters, and configuration logic. Paraglide compiles everything at build time, so your users only download the actual translated strings as simple JavaScript functions.&lt;/p&gt;

&lt;p&gt;This was the main reason I switched to Paraglide for my frontend-rendered sites. When every byte counts for performance, zero-runtime overhead makes a real difference.&lt;/p&gt;

&lt;p&gt;Server-side rendering? The bundle size advantage matters less. But for client-side apps, SPAs, and any frontend-heavy project, Paraglide's compile-time approach delivers measurable performance benefits.&lt;/p&gt;

&lt;p&gt;The type safety and framework flexibility are nice bonuses. The smaller bundles are why it's worth the switch.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>Know Your Dealer: npm's Security Features You're Not Using</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Sat, 05 Jul 2025 04:46:58 +0000</pubDate>
      <link>https://dev.to/cordlesswool/know-your-dealer-npms-security-features-youre-not-using-466o</link>
      <guid>https://dev.to/cordlesswool/know-your-dealer-npms-security-features-youre-not-using-466o</guid>
      <description>&lt;p&gt;I trust 700+ strangers with my production code. You probably do too.&lt;/p&gt;

&lt;p&gt;Every time you run &lt;code&gt;npm install&lt;/code&gt;, you're essentially saying "here, random people on the internet, please have access to my filesystem, network, and potentially my users' data." And honestly? That's fine. It's how the modern web works.&lt;/p&gt;

&lt;p&gt;npm didn't just make JavaScript development possible – it made it &lt;strong&gt;scalable&lt;/strong&gt;. The ability to pull in battle-tested solutions instead of reinventing every wheel turned JavaScript from a toy language into the backbone of the internet.&lt;/p&gt;

&lt;p&gt;But here's the thing: npm gave you security tools. You're just not using them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trust equation that actually works
&lt;/h2&gt;

&lt;p&gt;Let me be clear: I'm not here to scare you away from npm. I use pnpm myself (which is built on the same ecosystem), and I install packages daily. The JavaScript ecosystem's strength IS its openness.&lt;/p&gt;

&lt;p&gt;But there's a difference between informed trust and blind faith.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Informed trust:&lt;/strong&gt; "I install packages, and I know what I'm installing."&lt;br&gt;
&lt;strong&gt;Blind faith:&lt;/strong&gt; "I install packages and hope for the best."&lt;/p&gt;

&lt;p&gt;The tools to move from blind faith to informed trust are already sitting in your terminal. You just need to use them.&lt;/p&gt;
&lt;h2&gt;
  
  
  npm audit: Your dependency background check
&lt;/h2&gt;

&lt;p&gt;Let's start with the obvious one you've probably ignored: &lt;code&gt;npm audit&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This command scans your dependency tree against the Node Security Advisory database. It's free, it's built-in, and it finds real vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you'll typically see:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;found 12 vulnerabilities (3 low, 6 moderate, 2 high, 1 critical)
run `npm audit fix` to fix them, or `npm audit` for details
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The brutal truth:&lt;/strong&gt; Most projects have vulnerabilities. That's not necessarily a crisis – it's information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding what npm audit actually tells you
&lt;/h3&gt;

&lt;p&gt;Not all vulnerabilities are created equal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Critical/High:&lt;/strong&gt; These matter. Fix them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moderate:&lt;/strong&gt; Fix them when convenient, but don't panic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low:&lt;/strong&gt; Usually noise, but worth understanding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key is &lt;strong&gt;context&lt;/strong&gt;. A vulnerability in a development-only testing library? Probably fine. A vulnerability in your authentication middleware? Not fine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See details, not just summary&lt;/span&gt;
npm audit &lt;span class="nt"&gt;--audit-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;moderate

&lt;span class="c"&gt;# Focus on production dependencies only&lt;/span&gt;
npm audit &lt;span class="nt"&gt;--production&lt;/span&gt;

&lt;span class="c"&gt;# Get machine-readable output for automation&lt;/span&gt;
npm audit &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When npm audit fix actually works (and when it doesn't)
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;This auto-updates packages to secure versions. It works about 60-70% of the time for simple cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When it works:&lt;/strong&gt; Minor version updates that don't break APIs.&lt;br&gt;
&lt;strong&gt;When it doesn't:&lt;/strong&gt; Major version changes, peer dependency conflicts, or packages that simply haven't been updated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Always run your tests after &lt;code&gt;npm audit fix&lt;/code&gt;. Auto-updates can break things.&lt;/p&gt;

&lt;p&gt;For stubborn vulnerabilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See what would happen without doing it&lt;/span&gt;
npm audit fix &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

&lt;span class="c"&gt;# Force major version updates (dangerous)&lt;/span&gt;
npm audit fix &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  npm outdated: Know what you're missing
&lt;/h2&gt;

&lt;p&gt;Here's a command I actually use regularly:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This shows you which packages have newer versions available. It's not directly about security, but outdated packages are often vulnerable packages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The output tells you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current version installed&lt;/li&gt;
&lt;li&gt;Wanted version (respects your semver range)&lt;/li&gt;
&lt;li&gt;Latest version available&lt;/li&gt;
&lt;li&gt;Where it's defined (package.json vs. dependency)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;npm outdated&lt;/code&gt; monthly&lt;/li&gt;
&lt;li&gt;Update patch versions automatically&lt;/li&gt;
&lt;li&gt;Research major version updates before applying&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The dependency tree: Understanding what you actually installed
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows your complete dependency tree. Prepare to be surprised by how deep it goes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Just top-level dependencies&lt;/span&gt;
npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="c"&gt;# Find specific packages&lt;/span&gt;
npm &lt;span class="nb"&gt;ls &lt;/span&gt;package-name

&lt;span class="c"&gt;# Production dependencies only&lt;/span&gt;
npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; You might think you installed 10 packages, but you actually installed 200. Each one is a potential attack vector.&lt;/p&gt;

&lt;h2&gt;
  
  
  package-lock.json: Your security anchor
&lt;/h2&gt;

&lt;p&gt;That &lt;code&gt;package-lock.json&lt;/code&gt; file? It's not just for reproducible builds – it's a security feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without package-lock.json:&lt;/strong&gt; &lt;code&gt;npm install&lt;/code&gt; might pull different versions each time.&lt;br&gt;
&lt;strong&gt;With package-lock.json:&lt;/strong&gt; You get exactly the same dependency tree every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security implication:&lt;/strong&gt; A malicious package can't sneak into your project through a dependency update if your lockfile pins the exact versions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use this in CI/CD&lt;/span&gt;
npm ci

&lt;span class="c"&gt;# Not this&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npm ci&lt;/code&gt; installs exactly what's in your lockfile and fails if package.json and lockfile are out of sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical security workflow that doesn't suck
&lt;/h2&gt;

&lt;p&gt;Here's what I actually do (and what you should probably do):&lt;/p&gt;

&lt;h3&gt;
  
  
  Before adding new packages:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check the package health&lt;/span&gt;
npm view package-name

&lt;span class="c"&gt;# Look at download stats, maintainers, dependencies&lt;/span&gt;
npm view package-name downloads
npm view package-name dependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Monthly maintenance:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check for vulnerabilities&lt;/span&gt;
npm audit

&lt;span class="c"&gt;# Check for outdated packages&lt;/span&gt;
npm outdated

&lt;span class="c"&gt;# Update what makes sense&lt;/span&gt;
npm update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Before releases:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full security check&lt;/span&gt;
npm audit &lt;span class="nt"&gt;--audit-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;low

&lt;span class="c"&gt;# Ensure clean lockfile&lt;/span&gt;
npm ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The tools npm gives you (that you're probably not using)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  npm view: Research before you install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm view express
npm view express versions &lt;span class="nt"&gt;--json&lt;/span&gt;
npm view express repository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  npm explain: Understand why packages are there
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;This tells you which of your dependencies pulled in lodash, so you understand your dependency chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  npm doctor: Health check your setup
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Checks if your npm installation is healthy and properly configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to worry (and when not to)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Worry about:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Critical vulnerabilities in production dependencies&lt;/li&gt;
&lt;li&gt;Packages that haven't been updated in 2+ years&lt;/li&gt;
&lt;li&gt;Dependencies with excessive permissions&lt;/li&gt;
&lt;li&gt;Maintainers who disappear&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Don't panic about:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low-severity vulnerabilities in dev dependencies&lt;/li&gt;
&lt;li&gt;Prototype pollution warnings (usually not exploitable)&lt;/li&gt;
&lt;li&gt;Having "a lot" of dependencies (that's normal)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Red flags that actually matter
&lt;/h2&gt;

&lt;p&gt;After years of using npm/pnpm, here are the actual warning signs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Package red flags:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weekly downloads under 1000 (unless it's very niche)&lt;/li&gt;
&lt;li&gt;Last published more than 2 years ago&lt;/li&gt;
&lt;li&gt;Maintainer list is empty or suspicious&lt;/li&gt;
&lt;li&gt;Recent version jumps (1.2.3 → 15.0.0) without clear changelog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dependency red flags:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Packages that pull in hundreds of sub-dependencies&lt;/li&gt;
&lt;li&gt;Packages that require network access during install&lt;/li&gt;
&lt;li&gt;Packages with vague descriptions or no documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest assessment
&lt;/h2&gt;

&lt;p&gt;npm's security model isn't perfect. Supply chain attacks happen. Packages get compromised. Maintainers make mistakes or disappear.&lt;/p&gt;

&lt;p&gt;But the alternative – writing everything from scratch – isn't realistic. The JavaScript ecosystem's power comes from its openness and collaboration.&lt;/p&gt;

&lt;p&gt;The goal isn't perfect security (impossible) or zero dependencies (impractical). The goal is &lt;strong&gt;informed decisions&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your action plan
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This week:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npm audit&lt;/code&gt; on your main project&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;npm outdated&lt;/code&gt; and update what makes sense&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;npm ci&lt;/code&gt; to your deployment process&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Monthly:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;npm audit &amp;amp;&amp;amp; npm outdated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Research and update major dependencies&lt;/li&gt;
&lt;li&gt;Remove packages you're not actually using&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Before major releases:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Full audit with &lt;code&gt;npm audit --audit-level=low&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Verify production dependencies with &lt;code&gt;npm ls --production&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test everything after updates&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Know your dealer
&lt;/h2&gt;

&lt;p&gt;npm is an incredible tool that democratized code sharing and made modern web development possible. The fact that you can pull in battle-tested solutions with a single command is genuinely amazing.&lt;/p&gt;

&lt;p&gt;But like any powerful tool, it works best when you understand what it's actually doing.&lt;/p&gt;

&lt;p&gt;You don't need to become a security expert or audit every line of code in your node_modules. You just need to use the tools npm already gives you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Know your dealer.&lt;/strong&gt; Know what they're selling you. Make informed decisions.&lt;/p&gt;

&lt;p&gt;Your future self (and your users) will thank you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Using pnpm instead of npm? These principles apply there too – just replace &lt;code&gt;npm&lt;/code&gt; with &lt;code&gt;pnpm&lt;/code&gt; in the commands.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Fedora CoreOS Setup Guide – Ignition Files Without the Frustration</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Fri, 30 May 2025 16:40:21 +0000</pubDate>
      <link>https://dev.to/cordlesswool/fedora-coreos-setup-guide-ignition-files-without-the-frustration-23je</link>
      <guid>https://dev.to/cordlesswool/fedora-coreos-setup-guide-ignition-files-without-the-frustration-23je</guid>
      <description>&lt;p&gt;Fedora CoreOS setup usually doesn't fail because of complexity, but because of the missing entry point. The documentation is comprehensive - perfect as a reference, but not ideal for the first setup. Anyone who only occasionally installs CoreOS often faces the question: How do you actually get the Ignition file into the system? That's exactly what this guide explains.&lt;/p&gt;

&lt;p&gt;This article shows how to build a working Fedora CoreOS system from scratch. The Ignition file is created step by step – with SSH access and everything needed to get started. The structure is relatively simple and easy to understand, with just one SSH public key for the &lt;code&gt;core&lt;/code&gt; user.&lt;/p&gt;

&lt;p&gt;CoreOS works differently than traditional Linux installations – everything is defined upfront instead of configured afterwards. Once understood, the approach is convincing.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR - Quick Reference:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Ignition file:&lt;/strong&gt; Write YAML file with SSH key (&lt;code&gt;config.bu&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convert to JSON:&lt;/strong&gt; &lt;code&gt;docker run --rm -i quay.io/coreos/butane:release --strict &amp;lt; config.bu &amp;gt; config.ign&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start HTTP server:&lt;/strong&gt; &lt;code&gt;python3 -m http.server 8000&lt;/code&gt; in directory with .ign file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find IP address:&lt;/strong&gt; &lt;code&gt;ip addr show | grep "inet " | grep -v 127.0.0.1&lt;/code&gt; (Linux)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boot CoreOS ISO&lt;/strong&gt; and install: &lt;code&gt;sudo coreos-installer install /dev/sda --ignition-url http://IP:8000/config.ign --insecure-ignition&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reboot&lt;/strong&gt; - CoreOS is ready to go&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Creating the Ignition File
&lt;/h2&gt;

&lt;p&gt;The Ignition file is a JSON file that describes the complete system before it gets installed. Instead of creating users and configuring SSH keys after installation, everything happens automatically during the first boot.&lt;/p&gt;

&lt;p&gt;The easiest way is to write a human-readable YAML file and then convert it to JSON using Butane. For getting started, a minimal setup with SSH access for the default user is sufficient:&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;# config.bu&lt;/span&gt;
&lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fcos&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.6.0&lt;/span&gt;
&lt;span class="na"&gt;passwd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core&lt;/span&gt;
      &lt;span class="na"&gt;ssh_authorized_keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ssh-rsa&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AAAAB3NzaC1yc2EAAAA...&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;your-public-key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Three lines of configuration define SSH access for the default user &lt;code&gt;core&lt;/code&gt;. The &lt;code&gt;variant: fcos&lt;/code&gt; specifies that this is for Fedora CoreOS, the version determines the schema. CoreOS comes with the &lt;code&gt;core&lt;/code&gt; user already preconfigured – it just needs an SSH key.&lt;/p&gt;

&lt;p&gt;This YAML file (with &lt;code&gt;.bu&lt;/code&gt; extension) is then converted with Butane to a &lt;code&gt;.ign&lt;/code&gt; JSON file that CoreOS understands.&lt;/p&gt;

&lt;h3&gt;
  
  
  From YAML to JSON – The Butane Conversion
&lt;/h3&gt;

&lt;p&gt;The YAML file is human-readable, but CoreOS only understands JSON. That's where Butane comes in – a tool that handles the conversion and also checks the syntax.&lt;/p&gt;

&lt;p&gt;Butane runs easiest as a container. With Docker or Podman:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; quay.io/coreos/butane:release &lt;span class="nt"&gt;--strict&lt;/span&gt; &amp;lt; config.bu &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; config.ign
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, Butane can also be installed as a local binary if Docker is not available.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--strict&lt;/code&gt; parameter ensures that Butane aborts on errors instead of ignoring warnings.&lt;/p&gt;

&lt;p&gt;The result is a &lt;code&gt;config.ign&lt;/code&gt; file in JSON format that Ignition understands:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignition"&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;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.5.0"&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;"passwd"&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;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"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;"core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"sshAuthorizedKeys"&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="s2"&gt;"ssh-rsa AAAAB3NzaC1yc2EAAAA... your-public-key"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;.ign&lt;/code&gt; file is what CoreOS reads during the first boot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the Ignition File into the Installation
&lt;/h2&gt;

&lt;p&gt;Now we have the Ignition file - but how do we make it available to CoreOS during installation? This is where many people fail. CoreOS starts without prior configuration and needs the Ignition file already during the first boot.&lt;/p&gt;

&lt;p&gt;There are several ways, but the easiest is via URL. CoreOS can load the Ignition file from a web server. This sounds complicated, but with a local HTTP server it's done in minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local HTTP Server with Python
&lt;/h3&gt;

&lt;p&gt;Python has a built-in HTTP server. In the directory with the &lt;code&gt;config.ign&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Python 3&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8000

&lt;span class="c"&gt;# Python 2 (if Python 3 is not available)&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; SimpleHTTPServer 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the IP address of your machine depending on the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux&lt;/span&gt;
ip addr show | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"inet "&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; 127.0.0.1

&lt;span class="c"&gt;# macOS&lt;/span&gt;
ifconfig | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"inet "&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; 127.0.0.1

&lt;span class="c"&gt;# Windows (PowerShell)&lt;/span&gt;
ipconfig | findstr &lt;span class="s2"&gt;"IPv4"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the IP is for example &lt;code&gt;192.168.1.100&lt;/code&gt;, then the Ignition file is available at &lt;code&gt;http://192.168.1.100:8000/config.ign&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternative Methods
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;USB stick&lt;/strong&gt;: The Ignition file can also be provided on a USB stick&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud providers&lt;/strong&gt;: With cloud providers, the file is usually provided via their metadata service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network boot&lt;/strong&gt;: With PXE boot, the file can be provided via TFTP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For getting started, the HTTP server is the most straightforward approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performing the CoreOS Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Download ISO
&lt;/h3&gt;

&lt;p&gt;The Fedora CoreOS ISO is needed for installation. It's available on the &lt;a href="https://fedoraproject.org/coreos/download/" rel="noopener noreferrer"&gt;official download page&lt;/a&gt;. The "Bare Metal &amp;amp; Virtualized" variant is right for our purposes.&lt;/p&gt;

&lt;p&gt;Write the ISO to a USB stick or mount it in a VM. CoreOS boots directly into a live environment – no installation wizard, just a terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;The live environment automatically logs in as the &lt;code&gt;core&lt;/code&gt; user. The &lt;code&gt;coreos-installer&lt;/code&gt; command handles the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;coreos-installer &lt;span class="nb"&gt;install&lt;/span&gt; /dev/sda &lt;span class="nt"&gt;--ignition-url&lt;/span&gt; http://192.168.1.100:8000/config.ign &lt;span class="nt"&gt;--insecure-ignition&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/dev/sda&lt;/code&gt; – Target drive (VMs usually use &lt;code&gt;/dev/vda&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--ignition-url&lt;/code&gt; – URL to the Ignition file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--insecure-ignition&lt;/code&gt; – Required for HTTP without SSL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code&gt;lsblk&lt;/code&gt; to show all drives if unclear which is the right one.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Happens During Installation?
&lt;/h3&gt;

&lt;p&gt;The installer fetches the Ignition file, partitions the disk, and installs the CoreOS image. The Ignition file gets integrated into the system and executes during the first real boot.&lt;/p&gt;

&lt;p&gt;Takes only a few minutes. After reboot, the HTTP server can be stopped and is no longer needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing docker-compose
&lt;/h2&gt;

&lt;p&gt;After installation, tools like &lt;code&gt;vim&lt;/code&gt; or &lt;code&gt;docker-compose&lt;/code&gt; are missing. The first reflex is &lt;code&gt;dnf install&lt;/code&gt; as usual with Fedora – but that doesn't work. CoreOS has an immutable filesystem, the root system is read-only.&lt;/p&gt;

&lt;p&gt;Software is installed via &lt;code&gt;rpm-ostree&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install software&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;rpm-ostree &lt;span class="nb"&gt;install &lt;/span&gt;vim docker-compose

&lt;span class="c"&gt;# Reboot required&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reboot, the software is available. The system remains stable and can be easily rolled back if there are problems.&lt;/p&gt;

&lt;p&gt;Most software runs as containers – CoreOS is optimized for that. Only system tools like &lt;code&gt;vim&lt;/code&gt; or &lt;code&gt;docker-compose&lt;/code&gt; are installed directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Fedora CoreOS?
&lt;/h2&gt;

&lt;p&gt;CoreOS is optimized for container workloads. The immutable system prevents drift and makes updates predictable. Rolling releases keep the system automatically up to date.&lt;/p&gt;

&lt;p&gt;For pure container environments, CoreOS is leaner than a full Fedora. Fewer installed packages mean less attack surface and less maintenance overhead.&lt;/p&gt;

&lt;p&gt;The system boots fast and runs stable. Updates happen automatically in the background, a reboot activates the new version. If there are problems, rollback to the previous version is possible.&lt;/p&gt;

&lt;p&gt;CoreOS makes sense for everyone who primarily wants to run containers. For traditional software installation, a standard Fedora is better suited.&lt;/p&gt;

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

&lt;p&gt;The Fedora CoreOS setup is not complicated, but different from what you're used to. The critical point is getting the Ignition file into the installation. A Python HTTP server solves this elegantly.&lt;/p&gt;

&lt;p&gt;The switch from &lt;code&gt;dnf&lt;/code&gt; to &lt;code&gt;rpm-ostree&lt;/code&gt; takes some getting used to. But &lt;code&gt;rpm-ostree&lt;/code&gt; isn't needed that often anyway, since everything runs in containers.&lt;/p&gt;

&lt;p&gt;CoreOS is suitable for container workloads and minimal systems. Linux beginners should first gain experience with standard Fedora – CoreOS is a specialized tool.&lt;/p&gt;

&lt;p&gt;The test on Raspberry Pi is still pending, but could be interesting.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
    </item>
    <item>
      <title>Self-Host Your Own URL Shortener with Zero Database Setup</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Tue, 27 May 2025 15:35:54 +0000</pubDate>
      <link>https://dev.to/cordlesswool/self-host-your-own-url-shortener-with-zero-database-setup-575n</link>
      <guid>https://dev.to/cordlesswool/self-host-your-own-url-shortener-with-zero-database-setup-575n</guid>
      <description>&lt;p&gt;Self-hosting shouldn't require a PhD in DevOps. After trying various URL shorteners that demanded complex database setups and endless configuration files, I built &lt;strong&gt;&lt;a href="https://shrtn.io" rel="noopener noreferrer"&gt;shrtn.io&lt;/a&gt;&lt;/strong&gt; – a lightweight, self-hosted URL shortener that actually respects your time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Another URL Shortener?
&lt;/h2&gt;

&lt;p&gt;Most self-hosted solutions require MySQL/PostgreSQL setup, complex configurations, and heavy resource usage. shrtn.io takes a different approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQLite by default&lt;/strong&gt; – No database server required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SvelteKit-powered&lt;/strong&gt; – Lightning fast and resource-efficient
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single Docker container&lt;/strong&gt; – Deploy in seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible deployment&lt;/strong&gt; – Docker, Cloudflare Workers, or traditional hosting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features That Actually Matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Privacy-First Design&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flexible access control: public access, private (login required), or public-only&lt;/li&gt;
&lt;li&gt;Restrict access by email addresses or domains
&lt;/li&gt;
&lt;li&gt;Disable public interface for complete privacy&lt;/li&gt;
&lt;li&gt;Hide user login when running in public-only mode&lt;/li&gt;
&lt;li&gt;Your data stays on your server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Smart Link Protection&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic DNS verification&lt;/li&gt;
&lt;li&gt;Blocks internal/private IP addresses by default&lt;/li&gt;
&lt;li&gt;Configurable security policies&lt;/li&gt;
&lt;li&gt;Allow internal links via feature flag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Developer-Friendly&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple database options: SQLite, libSQL, Turso, Cloudflare D1&lt;/li&gt;
&lt;li&gt;Custom domain support&lt;/li&gt;
&lt;li&gt;Configurable TTL limits&lt;/li&gt;
&lt;li&gt;Clean API design&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started (The Easy Way)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Docker (Recommended):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;-p&lt;/span&gt; 3001:3001 &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--name&lt;/span&gt; my-shrtn &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;-v&lt;/span&gt; ~/shrtn-data:/data &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;ORIGIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://url.example.com &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PUBLIC_INSTANCE_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PUBLIC_ONLY
   cordlesswool/shrtn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cloudflare Workers:&lt;/strong&gt;&lt;br&gt;
Deploy directly to Cloudflare's edge network with D1 database integration.&lt;/p&gt;

&lt;p&gt;That's it. No database installation, no complex environment files, no headaches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup a private instance
&lt;/h3&gt;

&lt;p&gt;Check out the setup guide at &lt;a href="https://shrtn.io/setup" rel="noopener noreferrer"&gt;https://shrtn.io/setup&lt;/a&gt; to configure a private instance. All you need to do is set &lt;code&gt;PUBLIC_INSTANCE_MODE=PRIVATE&lt;/code&gt; and configure an email provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Self-Host Your URL Shortener?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Control your data&lt;/strong&gt; – No third-party analytics harvesting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom domains&lt;/strong&gt; – Perfect branding for your links
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No rate limits&lt;/strong&gt; – Create as many links as you need&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy compliance&lt;/strong&gt; – GDPR/CCPA friendly by design&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No vendor lock-in&lt;/strong&gt; – Export your data anytime&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;shrtn.io started as a side project but has grown to over 45 stars on GitHub (and counting!). The focus remains on simplicity and developer experience – features that solve real problems without bloat.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try shrtn.io today:&lt;/strong&gt; &lt;a href="https://shrtn.io" rel="noopener noreferrer"&gt;https://shrtn.io&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; Check out the setup guide and contribute to the project&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Self-hosting should be simple. URL shortening should be private. shrtn.io delivers both.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>docker</category>
      <category>sqlite</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Paraglide 2.0 Migration – From Framework Glue to Clean Abstraction</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Wed, 07 May 2025 08:11:00 +0000</pubDate>
      <link>https://dev.to/cordlesswool/paraglide-20-migration-from-framework-glue-to-clean-abstraction-4d1b</link>
      <guid>https://dev.to/cordlesswool/paraglide-20-migration-from-framework-glue-to-clean-abstraction-4d1b</guid>
      <description>&lt;p&gt;From time to time, it's necessary to bring all dependencies up to date. Most of them update easily. Paraglide.js? Definitely not.&lt;/p&gt;

&lt;p&gt;This post is not just a dry migration guide — it’s also about the headaches, surprises, and a few decisions you’ll have to make if you’re already deep into a SvelteKit project like I am with &lt;a href="https://shrtn.io" rel="noopener noreferrer"&gt;shrtn.io&lt;/a&gt;. And while we’re at it: Paraglide’s approach to minimalism still makes me smile — even if it makes the docs a bit too... minimal at times.&lt;/p&gt;

&lt;p&gt;That said, version 2.0 is a huge step forward. The documentation is better, the architecture is more maintainable, and the framework independence finally feels real. But not everything is an upgrade — especially not the developer experience when removing some of the SvelteKit-specific helpers.&lt;/p&gt;

&lt;p&gt;If you're here just for the code, feel free to skip ahead.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;✓ Paraglide 2.0 drops framework-specific packages — use the new Vite plugin.&lt;/p&gt;

&lt;p&gt;→ You’ll need to clean up old imports, configs, and the &lt;code&gt;&amp;lt;ParaglideJS&amp;gt;&lt;/code&gt; wrapper.&lt;/p&gt;

&lt;p&gt;! Language switching now requires &lt;code&gt;data-sveltekit-reload&lt;/code&gt; or a manual &lt;code&gt;setLocale()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&amp;gt;&amp;gt; Overall: Fewer moving parts, more clarity — but less convenience in some spots. Otherwise, buckle up: here’s what it took to get Paraglide 2.0 running cleanly in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's New in Paraglide 2.0
&lt;/h2&gt;

&lt;p&gt;Paraglide 2.0 brings several core changes and improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Updated to the inlang SDK v2&lt;/strong&gt;, now with support for variants (e.g. pluralization).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified API&lt;/strong&gt;, works across frameworks without the need for framework-specific bindings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports all major i18n strategies&lt;/strong&gt;, including cookie, URL, domain, local storage, and session-based resolution.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Additional Improvements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nested message keys&lt;/strong&gt;: Organize translations in structured hierarchies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-imports&lt;/strong&gt;: Translation keys accessed via &lt;code&gt;m.key&lt;/code&gt; no longer require a manual import.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible key naming&lt;/strong&gt;: Supports arbitrary key names (even emojis) via &lt;code&gt;m["🍌"]()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental migration&lt;/strong&gt;: You can gradually adopt Paraglide 2.0 in existing projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenancy support&lt;/strong&gt;: Routing can now vary by domain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compiler API exposed&lt;/strong&gt;: Enables advanced automation or custom tooling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customizable routing strategies&lt;/strong&gt;: Choose or mix strategies like cookie or URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experimental per-locale bundle splitting&lt;/strong&gt;: Potential for reduced bundle sizes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework-agnostic SSR middleware&lt;/strong&gt;: Works with SvelteKit, Next.js, and others.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Native Vite Plugin
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://inlang.com/m/gerre34r/library-inlang-paraglideJs" rel="noopener noreferrer"&gt;Paraglide.js&lt;/a&gt; now ships with a framework-agnostic Vite plugin. No more need for framework-specific packages like &lt;code&gt;@inlang/paraglide-sveltekit&lt;/code&gt;. I’m also using it in production on &lt;a href="https://dropanote.de" rel="noopener noreferrer"&gt;dropanote.de&lt;/a&gt;, my own personal website built with embodi — which had no specific Paraglide framework plugin. So having a framework-agnostic plugin is a real win here.&lt;/p&gt;

&lt;p&gt;Install the new package and remove the old SvelteKit-specific dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @inlang/paraglide-js
pnpm remove @inlang/paraglide-sveltekit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your &lt;code&gt;vite.config.ts&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;paraglideVitePlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@inlang/paraglide-js/vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sveltekit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@sveltejs/kit/vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tailwindcss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tailwindcss/vite&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;paraglideVitePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./project.inlang&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;outdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/lib/paraglide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cookie&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;baseLocale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nf"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;sveltekit&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/**/*.{test,spec}.{js,ts}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;strategy&lt;/code&gt; option defines the language resolution order. This is new in Paraglide 2.0.&lt;/p&gt;

&lt;p&gt;And yes — this plugin now works across frameworks. That’s actually a big win if you’re aiming for portability or want to reduce tech-specific glue code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: New Naming Convention
&lt;/h2&gt;

&lt;p&gt;Paraglide 2.0 introduces a few opinionated renamings. You’ll need to touch both the config and your code.&lt;/p&gt;

&lt;p&gt;Update &lt;code&gt;project.inlang/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;{
  "$schema": "https://inlang.com/schema/project-settings",
&lt;span class="gd"&gt;- "sourceLanguageTag": "en",
&lt;/span&gt;&lt;span class="gi"&gt;+ "baseLocale": "en",
&lt;/span&gt;&lt;span class="gd"&gt;- "languageTags": ["en", "de"],
&lt;/span&gt;&lt;span class="gi"&gt;+ "locales": ["en", "de"],
&lt;/span&gt;&lt;span class="gd"&gt;- "modules": [
-   "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
-   "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",
-   "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
-   "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js",
-   "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js"
- ],
&lt;/span&gt;&lt;span class="gi"&gt;+ "modules": [
+   "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
+   "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
+ ],
&lt;/span&gt;  "plugin.inlang.messageFormat": {
&lt;span class="gd"&gt;-   "pathPattern": "./messages/{languageTag}.json"
&lt;/span&gt;&lt;span class="gi"&gt;+   "pathPattern": "./messages/{locale}.json"
&lt;/span&gt;  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also update your imports:&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;// Before:&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;languageTag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$lib/paraglide/runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// After:&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getLocale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$lib/paraglide/runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The renaming makes things more consistent — and since you're upgrading anyway, now’s a good time for a bit of cleanup. A few search-and-replace rounds, maybe a lint check, and you're done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Remove &lt;code&gt;i18n.ts&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Yep, it’s gone. Delete &lt;code&gt;src/lib/i18n.ts&lt;/code&gt;. If that sounds harmless — it’s not. This file was probably imported across your app.&lt;/p&gt;

&lt;p&gt;Here’s how to replace its functionality:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;hooks.server.ts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Replace &lt;code&gt;i18n.handle()&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;paraglideMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$lib/paraglide/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paraglideHandle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;paraglideMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localizedRequest&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;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localizedRequest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;transformPageChunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;%lang%&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;i18n.reroute()&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Reroute&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@sveltejs/kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;deLocalizeUrl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$lib/paraglide/runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reroute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Reroute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deLocalizeUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pathname&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;
  
  
  &lt;code&gt;+layout.svelte&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;ParaglideJS&amp;gt;&lt;/code&gt; component is gone too. RIP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- &amp;lt;ParaglideJS {i18n}&amp;gt;
&lt;/span&gt;    {@render children()}
&lt;span class="gd"&gt;- &amp;lt;/ParaglideJS&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one hurts. The wrapper used to handle links and localization. Now, you're on your own — you’ll need to wrap links with &lt;code&gt;localizeHref()&lt;/code&gt; or &lt;code&gt;localizeUrl()&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@sveltejs/kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PageServerLoad&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./$types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;localizeHref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$lib/paraglide/runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PageServerLoad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;localizeHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works — but doesn’t feel like progress. What used to be automatic now needs to be handled manually, and that includes setting up language-aware routing and keeping link consistency across layouts. It adds responsibility without offering much in return, at least not immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Language Switching
&lt;/h2&gt;

&lt;p&gt;To switch languages manually — for example, in a language selector or after clicking a custom flag icon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;setLocale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$lib/paraglide/runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;setLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to support language switching via links — especially in a way that SvelteKit recognizes during navigation — make sure to include the &lt;code&gt;data-sveltekit-reload&lt;/code&gt; attribute. Without it, the routing won’t fully reset, and language state might not update as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
  &lt;span class="na"&gt;data-sveltekit-reload&lt;/span&gt;
  &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt;
  &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;
  &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;localizeHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&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="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  EN
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
  &lt;span class="na"&gt;data-sveltekit-reload&lt;/span&gt;
  &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt;
  &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"de"&lt;/span&gt;
  &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;localizeHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&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;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  DE
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use &lt;code&gt;setLocale()&lt;/code&gt; and a &lt;code&gt;preventDefault()&lt;/code&gt; if you want to stay in SPA-land — just make sure the updated locale is reflected in the URL as well, or the app might not behave as expected when reloading or sharing links.&lt;/p&gt;

&lt;p&gt;If you forget this step, SvelteKit might continue rendering the page in the previous language even after &lt;code&gt;setLocale()&lt;/code&gt; is called, especially after navigation or reloads. In short: &lt;code&gt;data-sveltekit-reload&lt;/code&gt; ensures your intent is fully respected. Your call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migration Notes &amp;amp; Pitfalls
&lt;/h2&gt;

&lt;p&gt;Here are a few things that caught me off guard or took more time than expected:&lt;/p&gt;

&lt;p&gt;~&amp;gt; Removing &lt;code&gt;&amp;lt;ParaglideJS&amp;gt;&lt;/code&gt; breaks existing localization logic — you’ll need to rebuild it manually.&lt;/p&gt;

&lt;p&gt;! Missing &lt;code&gt;data-sveltekit-reload&lt;/code&gt; can lead to language switches silently failing.&lt;/p&gt;

&lt;p&gt;# Key renaming (&lt;code&gt;languageTags&lt;/code&gt; → &lt;code&gt;locales&lt;/code&gt;, etc.) touches a lot of files — don’t underestimate it.&lt;/p&gt;




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

&lt;p&gt;The upgrade to Paraglide.js 2.0 brings fewer files, fewer dependencies, and more architectural clarity. But with that comes less convenience — especially if you relied on the opinionated SvelteKit integrations.&lt;/p&gt;

&lt;p&gt;Still, it’s worth the switch. You’ll end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a framework-independent i18n system&lt;/li&gt;
&lt;li&gt;strong type safety&lt;/li&gt;
&lt;li&gt;and less vendor lock-in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paraglide isn’t doing the magic for you anymore. Whether that’s a good thing depends on what you value: convenience or control. You now need to be more explicit — which can feel tedious, but also results in code that's easier to reason about and maintain in the long run.&lt;/p&gt;

</description>
      <category>paraglide</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to use generics in pipe-and-combine</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Thu, 05 Dec 2024 20:00:38 +0000</pubDate>
      <link>https://dev.to/cordlesswool/how-to-use-generics-in-pipe-and-combine-5a99</link>
      <guid>https://dev.to/cordlesswool/how-to-use-generics-in-pipe-and-combine-5a99</guid>
      <description>&lt;p&gt;This is the second article about &lt;a href="https://www.npmjs.com/package/pipe-and-combine?activeTab=readme" rel="noopener noreferrer"&gt;pipe-and-combine&lt;/a&gt; and will go more in depth and the newest feature added with version 0.7.x - using generics in pipes.&lt;/p&gt;

&lt;h1&gt;
  
  
  The problem
&lt;/h1&gt;

&lt;p&gt;First, it helps to understand the problem. When you use a generic function you just fine what you need to handle, but maybe you just want to add something and the upcoming function needs a value from the beginning. By default, TypeScript does not follow the hole line when you define something like this:&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;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;
  &lt;span class="nx"&gt;fu1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fu2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fu3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fu1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fu2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fu3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all these functions are generic like &lt;code&gt;&amp;lt;T extends { n: number }&amp;gt;(data: T) =&amp;gt; { ...data, b: !!data.n}&lt;/code&gt; TypeScript will follow only one step. So A of fu1 is defined as &lt;code&gt;{n: number}' and B as&lt;/code&gt;{a: number, b: boolean}'. This is fine if the next function only needs stuff that is explicitly defined, but if you need something from fu1 in fu3 that is not mentioned in fu2 but is piped through it, TypeScript will tell you that this value does not exist.&lt;/p&gt;

&lt;p&gt;The second problem is that you cannot access the generic value of any function to define the input more clearly.&lt;/p&gt;

&lt;p&gt;If we know that all functions will merge all the time, we could do something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;
  &lt;span class="nx"&gt;fu1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fu2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fu3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fu1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fu2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fu3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But then really all functions have to merge or the type will not match again.&lt;/p&gt;

&lt;h1&gt;
  
  
  G is the solution
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;UPDATE: g was renamed to enrich in version 0.9.0&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before you think in the wrong direction, &lt;code&gt;G&lt;/code&gt; does not stand for God, it is not that powerful...&lt;/p&gt;

&lt;p&gt;Ok, back to what &lt;code&gt;g&lt;/code&gt; really does and why we need it: Logically, it is a merge function. It calls your function and merges the output with the given object.&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;//GMerge is the type returned by the function g&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GMerge&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;O&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;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="nx"&gt;I&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;O&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GMerge&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;g&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="c1"&gt;// The return value will be of type GMerge&lt;/span&gt;
&lt;span class="c1"&gt;// When executed, g will call your function and merge the result with the data passed to g.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logically, the function is very simple, but the important part is the branding. With this, &lt;code&gt;pipe-and-combine&lt;/code&gt; knows how to handle it and how to define the type. So you will have the real type and not just the minimal one required by the function before.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to remove values
&lt;/h1&gt;

&lt;p&gt;Besides the &lt;code&gt;g&lt;/code&gt; function witch merge, there is an &lt;code&gt;omit&lt;/code&gt; and &lt;code&gt;pick&lt;/code&gt; that you can use directly in your pipe, but they are also generic and handled in a special way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pipe-and-combine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// remove the attributes a and b from the object&lt;/span&gt;
&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&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;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Pick the attributes a and b from the object and return a object with those attributes&lt;/span&gt;
&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&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;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Additional
&lt;/h1&gt;

&lt;p&gt;There are several other functions already implemented that you can read about on the npm page, and more will be added in the future. As you can read in the &lt;a href="https://dev.to/cordlesswool/pipe-the-shit-41kj"&gt;first article&lt;/a&gt; on `pipe-and-combine', even as the library grows, by using pipeing instead of chaining, unused code could be shaken off (tree shaking).&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Pipe the shit</title>
      <dc:creator>Wolfgang Rathgeb</dc:creator>
      <pubDate>Mon, 02 Dec 2024 20:49:55 +0000</pubDate>
      <link>https://dev.to/cordlesswool/pipe-the-shit-41kj</link>
      <guid>https://dev.to/cordlesswool/pipe-the-shit-41kj</guid>
      <description>&lt;p&gt;I've got lots of ideas for this article, but I'm not sure where to start. If you're interested in making code more readable and using tree shaking, you should definitely check out the article.&lt;/p&gt;

&lt;p&gt;If you've ever developed with JavaScript, you've probably come across this type of code.&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;someInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;someInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jump&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;thing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like this style because it could be very easy to read and you see what is connected. Plus, you don't have to move data from one function to another because it's all saved in the instance. &lt;/p&gt;

&lt;p&gt;On top of the fact that this only works if the functions aren't async, there's another issue. It's not going to be easy to tree shake the content if you're not using it. If you use this in the client, you have to move the whole library with all the connected functions, which is a huge overhead.&lt;/p&gt;

&lt;p&gt;So, how can we keep things readable, use async functions and let Vite and co. tree shake all unused code?&lt;/p&gt;

&lt;p&gt;The answer's right there in the headline: "pipe." &lt;/p&gt;

&lt;p&gt;From a functional standpoint, implementing pipe is pretty straightforward. However, the types did pose a challenge. I've put everything together into a library and published it on npm. &lt;a href="https://www.npmjs.com/package/pipe-and-combine" rel="noopener noreferrer"&gt;pipe-and-combine&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;by&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;dec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;by&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;multiplyBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;by&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;divideBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;by&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;toStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// prepare the pipeline&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;multiplyBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;divideBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;toStr&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;// Executing the pipeline&lt;/span&gt;
&lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One advantage is that it's not tied to any object, so you can use any function as long as the input and output match the function before and after.&lt;/p&gt;

&lt;p&gt;The example I've given is pretty simple, but at least you can pipe everything. You'll have the same options as with chaining and classes. Generic functions might be a little more tricky, but there's a solution. I'll cover this in more detail in another article.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>programming</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
