Sitemap
Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.

Flutter This Week: The Proxy That Holds Your Events Hostage, Errors That Vanish, and a Router Update to Test First

9 min readOct 21, 2026

--

Two tooling bugs that waste hours because they look like your bug, plus a go_router release worth a careful look.

Press enter or click to view image in full size
Flutter This Week: The Proxy That Holds Your Events Hostage, Errors That Vanish, and a Router Update to Test First

The most expensive bugs aren’t the ones that crash your app. They’re the ones that make you doubt yourself. You stare at your streaming endpoint for an hour, add logging, rewrite the parser, and eventually discover the problem was never in your code. It was in the tool.

This week’s Flutter issue tracker has two perfect examples. One is a web dev-server proxy that quietly holds your real-time events hostage. The other is a Windows build that fails with a message so generic it might as well say “something happened somewhere.” Both are reproducible, both have clear workarounds, and both are worth knowing about before they eat your afternoon.

After that, we’ll look at go_router 18.1.1, which fixes some genuinely painful shell-route problems, and finish with a testing habit that would have caught a lot of web navigation bugs before users did.

1. The web dev proxy that holds your events hostage

Status: reproducible, fix proposed but not merged
Affects: flutter run -d chrome / web-server with proxy rules in web_dev_config.yaml

Here’s the scenario. You’re building a Flutter web app that streams data: live notifications over Server-Sent Events, progress updates, or tokens from an LLM backend sent as NDJSON (one JSON object per line). To avoid CORS headaches in development, you route /api/ through Flutter's built-in dev-server proxy:

# web_dev_config.yaml
server:
proxy:
- prefix: "/api/"
target: "http://127.0.0.1:51880"

The backend sends its first event immediately. The browser receives… nothing. Then, when the backend finally closes the connection, everything arrives at once. Your “real-time” stream has become a very slow batch job.

The original report shows it neatly. The same request sent straight to the backend got its first line after 33 ms. Through Flutter’s proxy, the first line still hadn’t arrived after three seconds. The Flutter team confirmed it reproduces on the latest stable release.

Press enter or click to view image in full size
The dev proxy holds small streamed chunks until the stream closes

Why it happens

The proposed fix explains the root cause, and it’s a classic case of two reasonable defaults combining badly. Flutter’s dev server is built on the shelf package, and proxied responses come from shelf_proxy. Those responses don't carry the context flag that tells shelf_io to disable output buffering, so they go through dart:io's default 8 KB output buffer. A small SSE event or NDJSON line just sits there until the buffer fills or the backend closes the response.

The frustrating part is that you can’t fix it from your backend. Setting bufferOutput = false, flushing after every write and even sending X-Accel-Buffering: no all work for the backend's own socket, but the proxy has its own buffer downstream.

The proposed fix

Pull request #194934 sets shelf.io.buffer_output: false on proxied responses, so each chunk is written out as soon as it arrives. The author reports that in a local test with an SSE backend, the first event arrived after 24 ms in release mode and 141 ms in Wasm debug, compared with about 8 seconds without the change, which was simply when the backend closed the stream. The PR adds a regression test, and all 40 proxy tests pass. As of today it's still waiting for a reviewer's approval.

What to do until it lands

The simplest workaround is to bypass the dev proxy for streaming endpoints in development. Call the backend directly and enable CORS for your dev origin on the backend side. That’s exactly why the direct request in the bug report worked fine.

If you can’t change the backend, you can put your own streaming-friendly proxy in front of it during development. Some teams also send an initial padding comment larger than 8 KB on SSE streams to push the buffer past its limit, since larger chunks are written straight through. It works, but it’s a hack, so leave a comment for your future self.

One more thing worth checking while you’re here: this is a development proxy issue. Your production build doesn’t use it. But if production goes through nginx or a similar reverse proxy, that layer can buffer SSE too. The usual fix there is proxy_buffering off for streaming routes, or the X-Accel-Buffering: no response header. Same symptom, different culprit.

2. Windows builds that hide the actual error

Status: open, awaiting investigation
Affects: flutter build windows with packages that use native asset build hooks

Quick background first. Native assets are Dart’s newer way for packages to ship native code. A package includes a hook/build.dart script that runs during your build to compile a library, or, very commonly, to download a prebuilt binary for the target platform. Since Dart 3.10 / Flutter 3.38 this is the officially supported path, so more and more packages with C, C++ or Rust code are moving to it.

The newly reported problem: when a build hook fails on Windows, the real reason can get lost. The reporter’s example is a hook that tries to download a native binary and gets an HTTP 404. The hook throws a perfectly clear exception saying exactly that. But what you see from flutter build windows is only the generic message that building native assets failed. The 404, the URL and the stack trace are all swallowed somewhere between the hook and your terminal.

Get Wartelski.’s stories in your inbox

Join Medium for free to get updates from this writer.

If you’ve ever debugged a Windows build, you’ll recognize the experience. Visual Studio’s MSBuild wraps the Flutter build step, and errors from deep inside it tend to arrive as “custom build step failed” plus a wall of paths. This isn’t even a new complaint: an older issue titled “Flutter build windows doesn’t show actual errors” described a similar experience back in 2024 with plugin CMake steps. The new report includes a reproducible example and also points out related problems with diagnostic output on Linux, so it’s not purely a Windows quirk.

Why does this matter so much? Because a 404 is the kind of failure that has nothing to do with your code.

Maybe the package’s release assets moved, maybe a CDN hiccupped, maybe your company proxy blocks GitHub downloads. Without the underlying message, people reasonably assume their own project is broken and start deleting build folders and reinstalling Visual Studio. An hour later, they discover it was a missing file on someone else's server.

3. go_router 18.1.1: good fixes, test before you upgrade

If your app has a persistent bottom navigation bar, a side rail, or tabs that remember their own stack, you’re almost certainly using ShellRoute or StatefulShellRoute. And if you've used them in a big app, you've probably met at least one of the bugs this release fixes.

According to the changelog, 18.1.1:

  • Fixes ShellRoute page and navigator-key collisions when pushing between nested shell routes. The underlying PR found that a nested navigator was keyed by the hash code of its navigator key, so two different keys with the same hash could be treated as the same navigator. That's the kind of bug that shows up as a random "Multiple widgets used the same GlobalKey" crash once in a while and makes you question your life choices.
  • Preserves and updates loaded StatefulShellRoute branches when the routing configuration changes, including resetting branches that were removed. This matters if your routes change at runtime, for example after login, when feature flags load, or when an admin section appears.
  • Preserves branch and root navigator observers when rebuilding inactive or preloaded shell branches. If your analytics or logging observer “randomly” stopped seeing navigation in some tabs, this may be why.
  • Stops wrapping unrelated async errors that happen during redirects as GoException, which makes errors from your own redirect logic much easier to track down.

These are real improvements, especially for large apps with dynamic routing. So why not upgrade immediately?

The autocomplete catch

On October 20, a new issue was filed: “[go_router][web] Autocomplete field loses focus in ShellRoute with semantics enabled”. On Flutter web with the semantics tree enabled, an Autocomplete field inside a shell route can lose keyboard focus when its suggestions appear. In practice, the user types one letter, the suggestions pop up, and the field stops accepting input.

If that sounds familiar, it’s because a very similar bug, where Autocomplete lost focus with ensureSemantics() and MaterialApp.router, was reported earlier this year and closed. It's worth knowing that recent go_router releases have also touched how shell routes interact with semantics: 18.0.2 fixed shell chrome like side rails being dropped from the semantics tree. I'm not claiming that's the cause, the investigation hasn't concluded, but it's a reminder that shell routes, overlays and the web semantics tree interact in subtle ways.

Why does “semantics enabled” matter? On Flutter web, the semantics tree is what screen readers and other assistive technologies use, and it’s built as real DOM elements on top of the canvas. Many apps enable it permanently for accessibility, and it’s also switched on automatically when a user turns on a screen reader. So this isn’t an edge case. It’s the experience your users with assistive technology get, and it can break while every widget test stays green.

My recommendation: if you rely on shell routes, the 18.1.1 fixes are worth having, but treat the upgrade like a real change. Upgrade in a branch, run your app on the web with semantics enabled, and actually type into your search fields. Better still, automate that check, which brings us to the last section.

Engineering recommendation of the week: test the browser, not just the widgets

Here’s the uncomfortable truth the autocomplete issue exposes. Your widget tests can say “the text field has focus” while the real browser disagrees. Flutter widget tests run in Flutter’s own world. They don’t render the web semantics DOM, they don’t involve the browser’s focus model, and they don’t press the browser’s Back button. Bugs that live in the space between Flutter and the browser are invisible to them.

So for Flutter web apps with nested navigation, searchable controls and detail screens, I’d add a small set of browser-level regression tests with a tool like Playwright. You don’t need hundreds. One good scenario covering the riskiest flow catches a surprising number of regressions. It should verify that:

  1. A search input accepts text and keeps focus after suggestions appear.
  2. Selecting a suggestion updates the route to the intended screen.
  3. The browser’s Back button restores the previous screen without losing the UI state you expect.
  4. Returning to the search screen lets the input receive focus again.

The key detail is testing the actual browser, including the semantic DOM. To make that work, build your app with semantics enabled, which also happens to match what screen-reader users get.

import 'package:flutter/semantics.dart';

void main() {
if (const bool.fromEnvironment('E2E')) {
SemanticsBinding.instance.ensureSemantics();
}
runApp(const MyApp());
}

Then build with flutter build web --dart-define=E2E=true, serve the build/web folder, and point Playwright at it. Here’s a sketch of the scenario above:

import { test, expect } from '@playwright/test';

test('search keeps focus, navigates, and survives Back', async ({ page }) => {
await page.goto('/search');

// 1. Typing works and focus survives the suggestions overlay
const search = page.getByRole('textbox', { name: 'Search products' });
await search.click();
await search.pressSequentially('lap');
await expect(page.getByText('Laptop stand')).toBeVisible();
await search.pressSequentially('top');
await expect(search).toBeFocused();
await expect(search).toHaveValue('laptop');

// 2. Selecting a suggestion updates the route
await page.getByText('Laptop stand').click();
await expect(page).toHaveURL(/\/products\/laptop-stand/);

// 3. Browser Back restores the previous screen and its state
await page.goBack();
await expect(page).toHaveURL(/\/search/);
await expect(page.getByRole('navigation')).toBeVisible();

// 4. The input can receive focus again
await search.click();
await expect(search).toBeFocused();
});

A few practical notes. The exact roles and names depend on your Semantics labels and Flutter version, so open the app with semantics on and inspect the DOM once before writing selectors. Giving important controls clear labels makes the tests readable and your app more accessible, which is a nice two-for-one. Use your router's real URL strategy in toHaveURL; the example assumes path-based URLs. And run these tests whenever you bump Flutter, go_router or any package that touches navigation or overlays. That's exactly when this class of bug sneaks in.

The main lesson is that a green widget test suite tells you that Flutter is happy. It doesn’t tell you that Chrome, Safari or a screen reader is happy. For web apps, you need both.

None of this week’s issues are dramatic. No CVEs, no breaking APIs. But they’re exactly the kind of problems that quietly cost teams days: a streaming feature that “doesn’t work” only in development, a Windows build that fails without saying why, and a router upgrade that passes every test and still breaks search for screen-reader users.

The common thread is visibility. Know where your bytes are buffered, make sure errors reach a human, and test in the environment your users actually run.

--

Wartelski.
Wartelski.

Written by Wartelski.

Fullstack Flutter developer building cross-platform apps for startups. Passionate about web development, cybersecurity, and the latest tech trends. ©

Stackademic
Stackademic

Published in Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.