diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts
index f18001a0ae4..c5076534266 100644
--- a/packages/server-renderer/__tests__/render.spec.ts
+++ b/packages/server-renderer/__tests__/render.spec.ts
@@ -851,6 +851,49 @@ function testRender(type: string, render: typeof renderToString) {
expect(fn2).toBeCalledWith('async child error')
})
+ test('error handling w/ async setup, multiple component instances and template rendering error', async () => {
+ const fn = vi.fn()
+ const fn2 = vi.fn()
+
+ const asyncChildren = defineComponent({
+ async setup() {},
+ template: `
asyncChildren{{notDefined.value}}
`
+ })
+ const app = createApp({
+ name: 'App',
+ components: {
+ asyncChildren
+ },
+ template: ``,
+ errorCaptured(error) {
+ fn(error)
+ }
+ })
+
+ app.config.errorHandler = error => {
+ fn2(error)
+ }
+
+ let caughtError: any = null
+ try {
+ await renderToString(app)
+ } catch (error) {
+ caughtError = error
+ }
+
+ expect(caughtError).not.toBe(null)
+ expect(caughtError.message).toBe(
+ "Cannot read properties of undefined (reading 'value')"
+ )
+
+ expect(fn).toHaveBeenCalledTimes(0)
+ expect(fn2).toHaveBeenCalledTimes(0)
+
+ expect(
+ `Property "notDefined" was accessed during render but is not defined on instance`
+ ).toHaveBeenWarnedTimes(2)
+ })
+
// https://github.com/vuejs/core/issues/3322
test('effect onInvalidate does not error', async () => {
const noop = () => {}
diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts
index a1f327b4320..a82ce8aaf02 100644
--- a/packages/server-renderer/src/render.ts
+++ b/packages/server-renderer/src/render.ts
@@ -107,7 +107,13 @@ export function renderComponentVNode(
// Note: error display is already done by the wrapped lifecycle hook function.
.catch(() => {})
}
- return p.then(() => renderComponentSubTree(instance, slotScopeId))
+ const renderPromise = p.then(() =>
+ renderComponentSubTree(instance, slotScopeId)
+ )
+ // Note: error display is already done by the wrapped lifecycle hook function. But we must add a catch here to avoid unhandled promise rejection.
+ renderPromise.catch(() => {})
+
+ return renderPromise
} else {
return renderComponentSubTree(instance, slotScopeId)
}