| 1 | import { renderToStringAsync, generateHydrationScript } from "solid-js/web"; |
| 2 | import { StaticRouter } from "@solidjs/router"; |
| 3 | import App from "./app"; |
| 4 | import { routes } from "./routes"; |
| 5 | |
| 6 | const 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 | |
| 19 | async 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 | |
| 33 | export 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 | }; |