Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[🐞] Native form POST to a routeAction$ returns 500 in a production build when a layout has a routeLoader$ (rc.0)

Closed
#9,157 2 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@akasakariko is already working on this.

Since Oct 18, 2026.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
68/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript, vite
Domain
api, backend

Research direction

Start in packages/qwik-router/src/runtime/src/route-loaders.ts at getLoaderRequestEvent, especially the Request construction around line 975, and trace how a native action POST reaches the root layout loader. Check the existing route-loader tests for coverage of POST requests with a consumed body. Done means the production build renders the page with status 200 after a native form POST, while the action's side effect remains saved.

Written by the indexing model from the issue text.

Description

Which component is affected?

Qwik Router

Describe the bug

On 2.0.0-rc.0, a native form POST to a routeAction$ (no JS, or the user submits before the Qwik loader has run) returns 500 Internal Server Error in a production build.

The action itself runs and its side effect is saved. The 500 comes afterwards, while the page is rendered. So the user sees an error page for a write that succeeded, and is likely to submit again.

Expected: the page re-renders with status 200, as the starter's todo demo promises ("This little app works even when JavaScript is disabled").

It happens with the unmodified playground starter.

Request to /demo/todolist/?qaction=<id> vite preview (build) vite --mode ssr (dev)
POST, Accept: text/html (native form submit) 500, item is saved 200
POST, Accept: application/json (what the client runtime sends) 200 200

Also confirmed in Chrome with JavaScript disabled: submit shows the 500 page, and after a reload the item is in the list.

Reproduction

Stock starter, no changes: npm create qwik@rc playground ./repro (route /demo/todolist/)

Steps to reproduce
npm create qwik@rc playground ./repro
cd repro
npm install
npx qwik build preview
npx vite preview --port 6190

Then, in a second terminal:

# 1. read the action id from the form's action attribute
curl -s http://localhost:6190/demo/todolist/ | grep -o 'qaction=[A-Za-z0-9]*' | head -1

# 2. native form POST -> 500
curl -i -X POST "http://localhost:6190/demo/todolist/?qaction=<id>" \
  -H "Accept: text/html" \
  -H "Origin: http://localhost:6190" \
  --data "text=hello"

# 3. the item was saved anyway
curl -s http://localhost:6190/demo/todolist/ | grep -o hello

Or in a browser: open /demo/todolist/, disable JavaScript in DevTools, reload, add an item.

System Info
  System:
    OS: macOS 27.0
    CPU: (10) arm64 Apple M1 Pro
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 25.2.1
    npm: 11.6.2
    pnpm: 10.14.0
  Browsers:
    Chrome: 154.0.8037.93
    Safari: 27.0
  npmPackages:
    @qwik.dev/core: ^2.0.0-rc.0 => 2.0.0-rc.0
    @qwik.dev/router: ^2.0.0-rc.0 => 2.0.0-rc.0
    typescript: 5.9.3 => 5.9.3
    vite: 8.2.1 => 8.2.1
Additional Information

Cause. getLoaderRequestEvent builds a new Request from the root request when a loader's path or search differs from the page's:

https://github.com/QwikDev/qwik/blob/5c51580a7059881db63c2a4a7f02acd04d2a13b9/packages/qwik-router/src/runtime/src/route-loaders.ts#L975

const request = new Request(url, rootRequestEv.request);

On a native action POST the action has already read the body, so rootRequestEv.request.bodyUsed is true and the constructor throws.

I wrapped the global Request constructor to log the failing call in the starter's preview build:

new Request threw: TypeError: Response body object should not be disturbed or locked
new url:        http://localhost:6192/?qaction=tCQ608gnjnQ
source request: POST http://localhost:6192/demo/todolist/?qaction=tCQ608gnjnQ  bodyUsed=true
    at extractBody (node:internal/deps/undici/undici)
    at new Request (node:internal/deps/undici/undici)
    at ki (server/build/q-ClbfaIuI.js:471:3853)
    at Ri (server/build/q-ClbfaIuI.js:471:5870)
    at server/entry.preview.js:1:20984

The new URL has pathname /, so the failing call is for the root layout's loader (useServerTimeLoader in src/routes/layout.tsx), whose path differs from the page's /demo/todolist/. Any page under a layout that has a routeLoader$ looks affected.

Not Node-specific. We first hit this in a Cloudflare Workers build of our own app, where workerd reports TypeError: This ReadableStream is disturbed (has already been read from), and cannot be used as a body.

Regression. 2.0.0-beta.37 does not contain this line, and the same request returned 200 there in our app.

Possible fix. Don't pass the consumed body when creating the loader's request (copy the headers and signal only, or skip the body when bodyUsed is true). A loader should not need the action's body.

Dominant language
TypeScript
Stars
22.1k
Forks
1.4k
Avg merge
1d 19h
Merged PRs (30d)
123

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from QwikDev/qwik

All issues in QwikDev/qwik

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.