Skip to content

Commit 19c945a

Browse files
committed
feat(dev): add SSE-based browser auto-reload triggered by external callers for development environment
1 parent 6fa04d1 commit 19c945a

4 files changed

Lines changed: 63 additions & 0 deletions

File tree

src/app/api/dev/reload/route.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
let controller: ReadableStreamDefaultController | null = null;
2+
3+
export async function GET() {
4+
if (process.env.NODE_ENV !== "development") {
5+
return new Response("", { status: 404 });
6+
}
7+
8+
if (controller) {
9+
controller.enqueue("data: reload\n\n");
10+
return Response.json({ message: "reloaded" });
11+
}
12+
13+
return new Response(
14+
new ReadableStream({
15+
start(c) {
16+
controller = c;
17+
c.enqueue("data: connected\n\n");
18+
},
19+
cancel() {
20+
controller = null;
21+
}
22+
}),
23+
{
24+
headers: {
25+
"Content-Type": "text/event-stream",
26+
"Cache-Control": "no-cache"
27+
}
28+
}
29+
);
30+
}

src/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
url
1212
} from "../../config";
1313
import { HeadMetaComponent } from "../components/components";
14+
import DevTools from "../components/dev/DevTools";
1415
import { fullUrl } from "../utils/url";
1516
import ClientLayout from "./clientLayout";
1617

@@ -62,6 +63,7 @@ export default function RootLayout({
6263
}}
6364
/>
6465
<ClientLayout>{children}</ClientLayout>
66+
<DevTools />
6567
</body>
6668
</html>
6769
);

src/components/dev/DevTools.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use client";
2+
3+
import dynamic from "next/dynamic";
4+
5+
// Dynamic import with NODE_ENV check ensures dev-only components like ReloadBrowser
6+
// are completely excluded from the production bundle via tree-shaking.
7+
// This wrapper exists because layout.tsx is a Server Component, where
8+
// next/dynamic with { ssr: false } is not allowed.
9+
const ReloadBrowser =
10+
process.env.NODE_ENV === "development"
11+
? dynamic(() => import("./ReloadBrowser"), { ssr: false })
12+
: () => null;
13+
14+
export default function DevTools() {
15+
return <ReloadBrowser />;
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"use client";
2+
3+
import { useEffect } from "react";
4+
5+
export default function ReloadBrowser() {
6+
useEffect(() => {
7+
const es = new EventSource("/api/dev/reload");
8+
es.onmessage = (e) => {
9+
if (e.data === "reload") window.location.reload();
10+
};
11+
return () => es.close();
12+
}, []);
13+
14+
return null;
15+
}

0 commit comments

Comments
 (0)