HarperFast / HarperFast/nextjs

Plugin passes the raw Node request to Next, bypassing Harper's mount-relative path rewriting

Open
#61 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
4
Forks
2
PR merge metrics
No merged PRs in 30d

Description

## Summary

The plugin hands Next.js the **raw** Node request instead of the Harper `Request`, so it never sees the mount-relative path Harper computed. An app mounted at a `urlPath` therefore gives Next the un-stripped URL, which Next cannot route.

## What happens

Harper's router already does the right thing. `server/middlewareChain.ts` builds a separate middleware chain per `urlPath`/`host`, and when a mounted route matches it wraps the request with `stripPrefix(request, route.urlPath)` — a Proxy that rewrites `pathname` and `url` (keeping the original available as `originalPathname`) — then dispatches only that route's chain.

The plugin then reaches past that Proxy to the object underneath:

```js
// src/plugin.ts
requestHandler(request._nodeRequest, request._nodeResponse, urlParse(request._nodeRequest.url, true))
```

`_nodeRequest` is the underlying Node `IncomingMessage`. The Proxy rewrote `request.url`, not `request._nodeRequest.url`. So for an app mounted at `/foo`, a request to `/foo/file.html` reaches Next as `/foo/file.html` when Harper had already resolved it to `/file.html`.

## Suggested fix

`Request.withNodeAdapter()` exists for exactly this. It proxies `_nodeRequest` but overrides `method`, `url` and `headers` with the current Request's values — its docstring notes these "may have been modified by middleware" — and gives back a `ServerResponse` that captures status/headers/body:

```js
return request.withNodeAdapter((req, res) => requestHandler(req, res, urlParse(req.url, true)));
```

Two things to carry across from the adapter's contract:

- It resolves to `{ status, headers, body }` where `body` is a `PassThrough`. The docstring flags that an `error` listener **must** be attached before the body is consumed, or a connection reset after headers are sent throws an uncaught exception.
- The upgrade handler (`/_next/webpack-hmr`) uses `_nodeRequest` too. That is the upgrade path rather than the HTTP path, so it needs its own assessment rather than the same change applied blindly.

## Not sufficient on its own

Fixing the inbound path makes Next *route* correctly under a mount, but Next also **generates** URLs — page links and especially `/_next/*` asset paths. Those would still be emitted at the root and 404 outside the mount. Emitting prefixed URLs requires `basePath`, which Next bakes in at **build** time.

So the complete story for serving a Next app under a `urlPath` is:

1. this fix, so inbound requests are mount-relative; **and**
2. the app built with `basePath` matching its mount.

Worth deciding whether the plugin should require/inject `basePath` when a `urlPath` is configured, or fail loudly when they disagree — silently serving an app whose assets all 404 is the worst of the three.

## Why it matters now

This is the blocker for hosting multiple Next apps on one Harper instance via `urlPath` mounting, which is the "simpler apps" tier of the application-isolation work (HarperFast/harper#642, and the thread-isolation design that came out of it). Today a single Next app at the root works; two apps under different `urlPath`s do not.

🤖 Filed by Claude on behalf of Kris

Contributor guide

Open the contributing guide

Research direction

Start in src/plugin.ts and compare its HTTP request handling with server/middlewareChain.ts and Request.withNodeAdapter(). Verify the adapter contract, including the required body error listener, and assess the separate /_next/webpack-hmr upgrade path. Done means mounted requests reach Next with rewritten paths and the basePath behavior is explicitly decided without silently breaking assets.

Written by the indexing model from the issue text.

Assessment

Tech stack
nextjs, typescript
Domain
backend, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.