diff --git a/examples/app-router-cloudflare/app/effect-test/page.tsx b/examples/app-router-cloudflare/app/effect-test/page.tsx
new file mode 100644
index 000000000..c9a7d198f
--- /dev/null
+++ b/examples/app-router-cloudflare/app/effect-test/page.tsx
@@ -0,0 +1,26 @@
+"use client";
+
+import { useEffect, useState } from "react";
+
+// Regression test for https://github.com/cloudflare/vinext/issues/695
+// useEffect callbacks were never firing after RSC hydration because
+// createFromReadableStream was awaited before being passed to hydrateRoot,
+// which blocked hydration until the entire RSC stream was consumed.
+export default function EffectTestPage() {
+ const [fired, setFired] = useState(false);
+ const [count, setCount] = useState(0);
+
+ useEffect(() => {
+ setFired(true);
+ }, []);
+
+ return (
+
+
{fired ? "effect-fired" : "effect-pending"}
+
Count: {count}
+
+
+ );
+}
diff --git a/packages/vinext/src/server/app-browser-entry.ts b/packages/vinext/src/server/app-browser-entry.ts
index 3713228b1..da579840f 100644
--- a/packages/vinext/src/server/app-browser-entry.ts
+++ b/packages/vinext/src/server/app-browser-entry.ts
@@ -179,7 +179,7 @@ async function main(): Promise {
registerServerActionCallback();
const rscStream = await readInitialRscStream();
- const root = await createFromReadableStream(rscStream);
+ const root = createFromReadableStream(rscStream);
reactRoot = hydrateRoot(
document,
diff --git a/tests/e2e/cloudflare-workers/hydration.spec.ts b/tests/e2e/cloudflare-workers/hydration.spec.ts
index ed92bb98e..73b37f3ee 100644
--- a/tests/e2e/cloudflare-workers/hydration.spec.ts
+++ b/tests/e2e/cloudflare-workers/hydration.spec.ts
@@ -22,6 +22,19 @@ test.describe("Cloudflare Workers Hydration", () => {
void consoleErrors;
});
+ // Regression test for https://github.com/cloudflare/vinext/issues/695
+ // createFromReadableStream was awaited before hydrateRoot, blocking effects.
+ test("useEffect fires after RSC hydration", async ({ page, consoleErrors }) => {
+ await page.goto(`${BASE}/effect-test`);
+
+ // useEffect should fire and update the status from "effect-pending" to "effect-fired"
+ await expect(page.locator('[data-testid="effect-status"]')).toHaveText("effect-fired", {
+ timeout: 10_000,
+ });
+
+ void consoleErrors;
+ });
+
test("page with timestamp hydrates without mismatch", async ({ page, consoleErrors }) => {
// This test specifically verifies the fix for GitHub issue #61.
// The home page has a timestamp that would cause hydration mismatch