vercel / vercel/next.js

Getting uncaughtException: Error: aborted with SSE after client browser close the connection

Open
#56,529 5 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
JavaScript
Stars
142k
Forks
32.5k
Avg merge
2d 14h
Merged PRs (30d)
351

Description

Link to the code that reproduces this issue

https://codesandbox.io/p/sandbox/n-vlt865

To Reproduce

1- Create a route on the api folder inside the app directory like this:

export const runtime = "edge";

export async function POST() {
  console.log("Route: /api/sse triggered");

  const responseStream = new TransformStream();

  return new Response(responseStream.readable, {
    headers: {
      "Content-Type": "text/event-stream",
      Connection: "keep-alive",
      "Cache-Control": "no-cache, no-transform",
      "X-Accel-Buffering": "no",
      "Content-Encoding": "none",
      "Access-Control-Allow-Origin": "*",
    },
  });
}

2- Create a client to connect to the SSE route

'use client';
import {
  EventSourceMessage,
  EventStreamContentType,
  fetchEventSource,
} from '@microsoft/fetch-event-source';

class RetriableError extends Error {}
class FatalError extends Error {}

class SSEService extends EventTarget {
  constructor() {
    super();

    this.connect();
  }

  connect = async () => {
    await fetchEventSource('/api/sse', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        foo: 'bar',
      }),
      onmessage: this.onMessage,
      onclose: this.onClose,
      onopen: this.onOpen,
      onerror: this.onError,
      openWhenHidden: true,
    });
  };

  onMessage(msg: EventSourceMessage) {
    console.log(msg);
  }

  onClose() {
    console.log('closed');
    // if the server closes the connection unexpectedly, retry:
    throw new RetriableError();
  }

  async onOpen(response: Response) {
    console.log('open', response);
    if (
      response.ok &&
      response.headers.get('content-type') === EventStreamContentType
    ) {
      return; // everything's good
    } else if (
      response.status >= 400 &&
      response.status < 500 &&
      response.status !== 429
    ) {
      // client-side errors are usually non-retriable:
      throw new FatalError();
    } else {
      throw new RetriableError();
    }
  }

  onError(err: Error) {
    console.log('error', err);
    if (err instanceof FatalError) {
      throw err; // rethrow to stop the operation
    } else {
      // do nothing to automatically retry. You can also
      // return a specific retry interval here.
    }
  }
}

export default SSEService;

3- Use the client to connect inside a page

"use client";
import React, { useEffect, useRef } from "react";
import SSEService from "./services/SSEService";

export default function Home() {
  const ref = useRef<SSEService>();
  useEffect(() => {
    if (!ref.current) {
      ref.current = new SSEService();
    }
  }, []);

  return <div>page index</div>;
}

4- Refresh the page and you will get this error on the server console

 ⨯ uncaughtException: Error: aborted
    at connResetException (node:internal/errors:705:14)
    at abortIncoming (node:_http_server:642:17)
    at socketOnClose (node:_http_server:636:3)
    at Socket.emit (node:events:525:35)
    at TCP.<anonymous> (node:net:301:12)
    at TCP.callbackTrampoline (node:internal/async_hooks:130:17) {
  code: 'ECONNRESET'
}
Current vs. Expected behavior

Close gracefully the connection

Verify canary release
  • I verified that the issue exists in the latest Next.js canary release
Provide environment information
Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 22.6.0: Fri Sep 15 13:41:28 PDT 2023; root:xnu-8796.141.3.700.8~1/RELEASE_ARM64_T6020
Binaries:
  Node: 16.20.1
  npm: 8.19.4
  Yarn: 1.22.19
  pnpm: N/A
Relevant Packages:
  next: 13.5.3
  eslint-config-next: 13.5.3
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 4.9.5
Next.js Config:
  output: N/A
Which area(s) are affected? (Select all that apply)

App Router

Additional context

No response

Contributor guide

Open the contributing guide

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.

Research direction

Start by running the linked CodeSandbox and reproducing the refresh or browser-close behavior against the App Router /api/sse POST route. Read the edge route and the SSEService client disconnect path, then confirm when the server emits ECONNRESET. Done means the connection closes without an uncaughtException while the SSE route continues to work.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, next.js, node.js, react
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.