<?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: Andreas Bergström</title>
    <description>The latest articles on DEV Community by Andreas Bergström (@andreasbergstrom).</description>
    <link>https://dev.to/andreasbergstrom</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F153469%2F5c1970f5-0d22-46e9-af0c-6931bb91d856.jpg</url>
      <title>DEV Community: Andreas Bergström</title>
      <link>https://dev.to/andreasbergstrom</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andreasbergstrom"/>
    <language>en</language>
    <item>
      <title>The best bug reports were written by the suspect</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Fri, 12 Jun 2026 04:47:42 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/the-best-bug-reports-were-written-by-the-suspect-2cd5</link>
      <guid>https://dev.to/andreasbergstrom/the-best-bug-reports-were-written-by-the-suspect-2cd5</guid>
      <description>&lt;p&gt;Our e-commerce rule engine holds risky invoice orders for human review, and the reviewers were drowning in false alarms. So we added an LLM second opinion — advisory only: a verdict, a confidence score, and a written rationale next to the hold reasons. The human still decides, and the model can never touch the ship path.&lt;/p&gt;

&lt;p&gt;The surprise wasn't the triage speedup. It was that when reviewers disagreed with the model, about half the investigations ended in &lt;em&gt;our&lt;/em&gt; code: a years-old off-by-one that flagged invoices as overdue on their own due date, "outstanding credit" that was actually unshipped prepayment orders, a payload field that had silently been zero forever, and a loyalty-credit loophole that had been quietly farmable for years.&lt;/p&gt;

&lt;p&gt;The full post covers the architecture (de-identified payloads, prompts versioned in the database, eval fixtures that deliberately freeze time) and why a model that must explain itself turns out to be a continuous audit of your feature pipeline.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/advisory-llm-verdicts-on-held-invoice-orders" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>openai</category>
      <category>ai</category>
      <category>security</category>
    </item>
    <item>
      <title>The X-Inertia cache trap: Cloudflare, Vary, and edge-cached HTML</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Thu, 11 Jun 2026 19:37:26 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/the-x-inertia-cache-trap-cloudflare-vary-and-edge-cached-html-90e</link>
      <guid>https://dev.to/andreasbergstrom/the-x-inertia-cache-trap-cloudflare-vary-and-edge-cached-html-90e</guid>
      <description>&lt;p&gt;I clicked the logo on one of my Laravel + Inertia sites and the homepage rendered &lt;em&gt;inside Inertia's error modal&lt;/em&gt; — in production, on a 200 response. The cause: Inertia serves HTML and JSON under the same URL, my app had opted its pages into Cloudflare's edge cache with &lt;code&gt;s-maxage&lt;/code&gt;, and Cloudflare ignores &lt;code&gt;Vary: X-Inertia&lt;/code&gt; when deciding cache hits. The edge happily handed cached HTML to an XHR that expected a JSON page object.&lt;/p&gt;

&lt;p&gt;The write-up walks through the live curl evidence (&lt;code&gt;cf-cache-status: HIT&lt;/code&gt; for an &lt;code&gt;X-Inertia: true&lt;/code&gt; request), why the &lt;code&gt;no-store&lt;/code&gt; I already had on the JSON responses couldn't prevent it (store-time vs lookup-time defenses), the one-line fix, and the Cloudflare Cache Rule that lets you keep edge-cached HTML if you control your own zone. Plus a thirty-second curl test to check whether your own Inertia app is affected.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/inertia-cloudflare-cache-poisoning" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>inertia</category>
      <category>cloudflare</category>
      <category>caching</category>
    </item>
    <item>
      <title>The Last Human-First Programming Language</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Wed, 06 May 2026 19:24:49 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/the-last-human-first-programming-language-24gl</link>
      <guid>https://dev.to/andreasbergstrom/the-last-human-first-programming-language-24gl</guid>
      <description>&lt;p&gt;Programming spent forty years climbing away from the machine — garbage collection, ORMs, dynamic typing, magical frameworks — trading runtime cost for human comfort while a person was at the keyboard. If LLMs are writing most of the code, the next generation of languages won't optimise for what's pleasant to type, but for what an agent can synthesise, inspect, test, and repair without anyone stepping in.&lt;/p&gt;

&lt;p&gt;The full post argues this isn't "we all go back to C" — it's that visibility at the call site, not abstraction level, becomes the new design axis. SQL keeps its abstraction; ORMs that materialise seventeen joins from one expression don't. Walks through the AI adoption tax on new languages, why Python complicates the picture, and ends on a falsifiable bet about which TypeScript and Java backends gain share by 2029.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/the-last-human-first-programming-language" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>programming</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Cutting a MySQL table in half with one line</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Mon, 04 May 2026 19:46:45 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/cutting-a-mysql-table-in-half-with-one-line-3aom</link>
      <guid>https://dev.to/andreasbergstrom/cutting-a-mysql-table-in-half-with-one-line-3aom</guid>
      <description>&lt;p&gt;We had a wide MySQL table — 8 million rows, 8 GB on disk, mostly index — about to grow 10× from a historical backfill. The instinct was to normalise: pull repeated strings into dimension tables. Classic relational hygiene. But twenty minutes spent measuring cardinality and average length per column pointed somewhere different.&lt;/p&gt;

&lt;p&gt;The biggest single column by raw bytes was a high-cardinality, near-unique click identifier from ad platforms. Normalising it would have moved the bytes around without removing them. Meanwhile InnoDB's &lt;code&gt;ROW_FORMAT=COMPRESSED&lt;/code&gt; finds patterns &lt;em&gt;within&lt;/em&gt; strings — particularly devastating on indexed URL-shaped columns — and it ships in a one-line &lt;code&gt;ALTER TABLE&lt;/code&gt; with &lt;code&gt;ALGORITHM=INPLACE, LOCK=NONE&lt;/code&gt;, no application code changes, fully reversible.&lt;/p&gt;

&lt;p&gt;Result on the production table: 8.18 GB → 3.63 GB, with the index side compressing 59%. The full post covers the cardinality measurements, why "won't reads get slower?" usually goes the right way for analytics workloads, and when to actually reach for normalisation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/cutting-a-mysql-table-in-half-with-one-line" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>database</category>
      <category>performance</category>
    </item>
    <item>
      <title>Wiring Prisma 7 into TanStack Start on Bun</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Mon, 04 May 2026 19:46:05 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/wiring-prisma-7-into-tanstack-start-on-bun-1enp</link>
      <guid>https://dev.to/andreasbergstrom/wiring-prisma-7-into-tanstack-start-on-bun-1enp</guid>
      <description>&lt;p&gt;TanStack Start, Prisma 7, and Bun fit together cleanly, but there are a handful of small decisions you want to get right before the code lands in production. This post is the list I'd want written down before starting.&lt;/p&gt;

&lt;p&gt;Highlights: Prisma 7 moved the runtime datasource into &lt;code&gt;prisma.config.ts&lt;/code&gt; (the &lt;code&gt;url = env(...)&lt;/code&gt; form in &lt;code&gt;schema.prisma&lt;/code&gt; no longer works); &lt;code&gt;@prisma/adapter-pg&lt;/code&gt; is the shortest path on Bun and turns connection behaviour into plain &lt;code&gt;pg&lt;/code&gt; knowledge; the stash-on-&lt;code&gt;globalThis&lt;/code&gt; singleton is non-negotiable under Vite HMR; and &lt;code&gt;@map&lt;/code&gt; / &lt;code&gt;@@map&lt;/code&gt; give you snake_case in Postgres and camelCase in TypeScript with zero runtime cost.&lt;/p&gt;

&lt;p&gt;The trickiest one is keeping Prisma out of the client bundle. &lt;code&gt;createServerFn&lt;/code&gt; handles the obvious case, but a shared service module that's transitively reachable from the client graph still needs a dynamic &lt;code&gt;import('./db')&lt;/code&gt; to keep Vite from trying to bundle Node built-ins.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/wiring-prisma-7-into-tanstack-start-on-bun" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>prisma</category>
      <category>tanstack</category>
      <category>bunjs</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Shipping TanStack Start and Bun to Railway</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Mon, 04 May 2026 19:45:24 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/shipping-tanstack-start-and-bun-to-railway-2o7k</link>
      <guid>https://dev.to/andreasbergstrom/shipping-tanstack-start-and-bun-to-railway-2o7k</guid>
      <description>&lt;p&gt;Railway's Nixpacks autobuild detects Bun projects fine, but it can't sequence the combination this site needs: &lt;code&gt;prisma generate&lt;/code&gt; at build time, Vite + the TanStack Start plugin, a custom &lt;code&gt;server.ts&lt;/code&gt; entry, and &lt;code&gt;prisma migrate deploy&lt;/code&gt; on boot. A four-stage Dockerfile is simpler than teaching Nixpacks all of that.&lt;/p&gt;

&lt;p&gt;The full post walks through the recipe: two parallel &lt;code&gt;bun install&lt;/code&gt; stages (one full, one production-only), a build stage that runs &lt;code&gt;prisma generate&lt;/code&gt; against a dummy &lt;code&gt;DATABASE_URL&lt;/code&gt;, a lean runtime that layers the generated Prisma client on top of &lt;code&gt;deps-prod&lt;/code&gt;'s &lt;code&gt;node_modules&lt;/code&gt;, and an entrypoint that runs migrations before &lt;code&gt;exec&lt;/code&gt;-ing into Bun so the container's PID 1 shuts down cleanly on Railway's SIGTERM.&lt;/p&gt;

&lt;p&gt;Plus the small Railway-side bits: how to wire the Postgres reference variable, why you should bind explicitly to &lt;code&gt;0.0.0.0&lt;/code&gt;, and the COPY-order detail that determines whether your runtime sees the right Prisma client.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/shipping-tanstack-start-and-bun-to-railway" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>railway</category>
      <category>docker</category>
      <category>bunjs</category>
      <category>tanstack</category>
    </item>
    <item>
      <title>Keeping the Prisma CLI out of a Bun runtime image</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Mon, 04 May 2026 19:43:22 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/keeping-the-prisma-cli-out-of-a-bun-runtime-image-1nb</link>
      <guid>https://dev.to/andreasbergstrom/keeping-the-prisma-cli-out-of-a-bun-runtime-image-1nb</guid>
      <description>&lt;p&gt;This site's runtime Docker image was 423 MB. After one Bun install flag and a one-line Dockerfile change, it's 267 MB — no functional change, just less Prisma CLI tooling sitting on disk that the server never imports.&lt;/p&gt;

&lt;p&gt;The interesting part is &lt;em&gt;why&lt;/em&gt; the obvious fix (moving &lt;code&gt;prisma&lt;/code&gt; to &lt;code&gt;devDependencies&lt;/code&gt;) doesn't do anything on its own. &lt;code&gt;@prisma/client&lt;/code&gt; declares &lt;code&gt;prisma&lt;/code&gt; as an &lt;em&gt;optional peer dependency&lt;/em&gt;, and Bun installs optional peers by default. From there, transitive deps drag in &lt;code&gt;@prisma/engines&lt;/code&gt;, &lt;code&gt;@prisma/studio-core&lt;/code&gt;, &lt;code&gt;@electric-sql/pglite&lt;/code&gt;, and ~160 MB of code the server never imports.&lt;/p&gt;

&lt;p&gt;The fix is &lt;code&gt;bun install --omit=peer&lt;/code&gt;, plus narrowing the runtime stage's &lt;code&gt;COPY --from=builder /app/node_modules/@prisma&lt;/code&gt; line to just &lt;code&gt;@prisma/client&lt;/code&gt;. The full post covers the trap, the fix, and where migrations have to run once the CLI is gone from the runtime image.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/keeping-the-prisma-cli-out-of-a-bun-runtime-image" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bunjs</category>
      <category>prisma</category>
      <category>docker</category>
      <category>railway</category>
    </item>
    <item>
      <title>The LLM-shaped hole in your XGBoost pipeline</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Mon, 04 May 2026 19:43:19 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/the-llm-shaped-hole-in-your-xgboost-pipeline-3m7f</link>
      <guid>https://dev.to/andreasbergstrom/the-llm-shaped-hole-in-your-xgboost-pipeline-3m7f</guid>
      <description>&lt;p&gt;Every team shipping a tabular ML model gets the same question from leadership eventually: &lt;em&gt;"Have we tried GPT for this?"&lt;/em&gt; The honest answer is that gradient-boosted trees still beat everything else on tabular prediction, and that hasn't changed because LLMs got bigger. TabPFN, FT-Transformer, and friends are interesting, but the short list of serious alternatives stays short.&lt;/p&gt;

&lt;p&gt;There &lt;em&gt;is&lt;/em&gt; an LLM-shaped hole in most XGBoost pipelines — it's just not where most people put it. The pattern that works is LLMs &lt;em&gt;upstream&lt;/em&gt; of trees: embeddings as features, text-to-features extraction over unstructured fields, and tool-using agents to fill enrichment gaps. None of these touch the prediction path; they all feed signal into the model that does.&lt;/p&gt;

&lt;p&gt;The full post walks through cost math, what each pattern is worth in practice, and the cases where the empirical answer turns out to be "no, the embeddings don't help."&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/the-llm-shaped-hole-in-your-xgboost-pipeline" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Using FastMCP with OpenAI and Avoiding Session Termination Issues</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Sun, 10 Aug 2025 10:32:13 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/using-fastmcp-with-openai-and-avoiding-session-termination-issues-k3h</link>
      <guid>https://dev.to/andreasbergstrom/using-fastmcp-with-openai-and-avoiding-session-termination-issues-k3h</guid>
      <description>&lt;p&gt;If you're plugging a FastMCP server into OpenAI's Responses API and your sessions keep dying with &lt;code&gt;Session has been terminated&lt;/code&gt;, the fix is one flag: &lt;code&gt;stateless_http=True&lt;/code&gt; when you instantiate &lt;code&gt;FastMCP(...)&lt;/code&gt;. OpenAI sends a &lt;code&gt;DELETE&lt;/code&gt; after each call without recreating the session, so anything stateful gets terminated between requests.&lt;/p&gt;

&lt;p&gt;The full post covers the JSON-RPC error you'll actually see, the minimal server change to make it work, and the caveats — session state and certain bi-directional streaming patterns drop out in stateless mode. Worth a quick read if you're hitting the same one-call-then-broken behaviour.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/fastmcp-openai-session-termination" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>fastmcp</category>
      <category>openai</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Redirecting mobile users to App or Play Store in NextJS</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Mon, 27 Nov 2023 19:53:11 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/redirecting-mobile-users-to-app-or-play-store-using-nextjs-3pp1</link>
      <guid>https://dev.to/andreasbergstrom/redirecting-mobile-users-to-app-or-play-store-using-nextjs-3pp1</guid>
      <description>&lt;p&gt;If you're promoting a mobile app for both iOS and Android and want a single URL or QR code that drops users into the correct store, you don't need a 3rd-party redirect service. A NextJS middleware that matches &lt;code&gt;userAgent&lt;/code&gt; against &lt;code&gt;iP(hone|ad|od)&lt;/code&gt; and &lt;code&gt;Android&lt;/code&gt; regexes does it in ~15 lines, runs before any render, and keeps the contact point — and any SEO it earns — on your own domain.&lt;/p&gt;

&lt;p&gt;The full post walks through the &lt;code&gt;middleware.ts&lt;/code&gt; plus the placeholder &lt;code&gt;app/get.tsx&lt;/code&gt; page that triggers it, with notes on the iOS-vs-Android UX quirk and why owning that redirect matters once QR codes are out in the wild and outlive your vendor relationships.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/nextjs-mobile-app-store-redirect" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>mobile</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Python del vs assigning to None</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Sat, 11 Nov 2023 16:37:38 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/python-del-vs-assigning-to-none-5387</link>
      <guid>https://dev.to/andreasbergstrom/python-del-vs-assigning-to-none-5387</guid>
      <description>&lt;p&gt;Both &lt;code&gt;x = None&lt;/code&gt; and &lt;code&gt;del x&lt;/code&gt; look like ways to release a variable in Python, but they aren't equivalent. &lt;code&gt;x = None&lt;/code&gt; keeps the name bound and just rebinds it to &lt;code&gt;NoneType&lt;/code&gt; (still ~16 bytes); &lt;code&gt;del x&lt;/code&gt; removes the binding entirely so the name itself is gone. Touch &lt;code&gt;x&lt;/code&gt; after &lt;code&gt;del&lt;/code&gt; and you get a &lt;code&gt;NameError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The full post walks through a worked example with &lt;code&gt;sys.getsizeof&lt;/code&gt; and &lt;code&gt;gc.collect()&lt;/code&gt; showing the byte difference, then disassembles both versions with &lt;code&gt;dis.dis&lt;/code&gt; so you can see &lt;code&gt;del&lt;/code&gt; compiles to a single &lt;code&gt;DELETE_FAST&lt;/code&gt; while assigning to &lt;code&gt;None&lt;/code&gt; becomes a &lt;code&gt;LOAD_CONST&lt;/code&gt; + &lt;code&gt;STORE_FAST&lt;/code&gt; pair.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/python-del-vs-none" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>performance</category>
    </item>
    <item>
      <title>Node 21 brings built-in WebSocket support</title>
      <dc:creator>Andreas Bergström</dc:creator>
      <pubDate>Mon, 16 Oct 2023 18:19:32 +0000</pubDate>
      <link>https://dev.to/andreasbergstrom/node-21-might-have-websockets-built-in-24f3</link>
      <guid>https://dev.to/andreasbergstrom/node-21-might-have-websockets-built-in-24f3</guid>
      <description>&lt;p&gt;Node 21 finally ships with a built-in WebSocket client — no more pulling in &lt;code&gt;ws&lt;/code&gt; or &lt;code&gt;socket.io-client&lt;/code&gt; for a basic browser-style API on the server side. Deno and Bun have shipped this for a while; this closes the gap.&lt;/p&gt;

&lt;p&gt;The full post (written just ahead of the v21 release) tracks the upstream Undici PR and the long-running Node issue thread that pushed it across the finish line, with a link to the v21 release announcement once it landed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://andreasbergstrom.dev/posts/node-21-built-in-websocket" rel="noopener noreferrer"&gt;andreasbergstrom.dev&lt;/a&gt; — read the full post there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
    </item>
  </channel>
</rss>
