1import { renderToStringAsync, generateHydrationScript } from "solid-js/web";
2import { StaticRouter } from "@solidjs/router";
3import App from "./app";
4import { routes } from "./routes";
5
6const FALLBACK_HTML = `<!DOCTYPE html>
7<html lang="en">
8<head>
9<meta charset="utf-8" />
10<meta name="viewport" content="width=device-width, initial-scale=1" />
11<title>Solid App</title>
12<!--ssr-head-->
13</head>
14<body>
15<div id="root"><!--ssr-outlet--></div>
16</body>
17</html>`;
18
19async function getTemplate(
20 env: { ASSETS: Fetcher },
21 origin: string,
22): Promise<string> {
23 try {
24 const res = await env.ASSETS.fetch(new Request(origin + "/index.html"));
25 if (res.ok) {
26 const text = await res.text();
27 if (text.length > 0) return text;
28 }
29 } catch {}
30 return FALLBACK_HTML;
31}
32
33export default {
34 async fetch(request: Request, env: { ASSETS: Fetcher }): Promise<Response> {
35 try {
36 const url = new URL(request.url);
37 const pathname = url.pathname;
38
39 // Serve static assets via the assets binding
40 if (pathname.startsWith("/assets/") || pathname.endsWith(".ico")) {
41 return env.ASSETS.fetch(request);
42 }
43
44 // Get the HTML template (from assets if available, fallback to bare HTML)
45 const template = await getTemplate(env, url.origin);
46
47 // Render the SolidJS app to HTML on the server
48 const appHtml = await renderToStringAsync(() => (
49 <StaticRouter
50 url={pathname}
51 root={(props) => <App>{props.children}</App>}
52 >
53 {routes}
54 </StaticRouter>
55 ));
56
57 // Inject the server-rendered HTML and hydration script
58 const html = template
59 .replace("<!--ssr-head-->", generateHydrationScript())
60 .replace("<!--ssr-outlet-->", appHtml);
61
62 return new Response(html, {
63 headers: { "Content-Type": "text/html; charset=utf-8" },
64 });
65 } catch (e: any) {
66 return new Response(`SSR Error: ${e.message}\n\n${e.stack}`, {
67 status: 500,
68 headers: { "Content-Type": "text/plain" },
69 });
70 }
71 },
72};