diff options
Diffstat (limited to 'frontend/app')
| -rw-r--r-- | frontend/app/entry.client.tsx | 12 | ||||
| -rw-r--r-- | frontend/app/entry.server.tsx | 124 | ||||
| -rw-r--r-- | frontend/app/root.tsx | 35 | ||||
| -rw-r--r-- | frontend/app/routes/_index.tsx | 9 | ||||
| -rw-r--r-- | frontend/app/tailwind.css | 3 |
5 files changed, 183 insertions, 0 deletions
diff --git a/frontend/app/entry.client.tsx b/frontend/app/entry.client.tsx new file mode 100644 index 0000000..442da4f --- /dev/null +++ b/frontend/app/entry.client.tsx @@ -0,0 +1,12 @@ +import { RemixBrowser } from "@remix-run/react"; +import { startTransition, StrictMode } from "react"; +import { hydrateRoot } from "react-dom/client"; + +startTransition(() => { + hydrateRoot( + document, + <StrictMode> + <RemixBrowser /> + </StrictMode>, + ); +}); diff --git a/frontend/app/entry.server.tsx b/frontend/app/entry.server.tsx new file mode 100644 index 0000000..77b6f15 --- /dev/null +++ b/frontend/app/entry.server.tsx @@ -0,0 +1,124 @@ +import { PassThrough } from "node:stream"; + +import type { EntryContext } from "@remix-run/node"; +import { createReadableStreamFromReadable } from "@remix-run/node"; +import { RemixServer } from "@remix-run/react"; +import { isbot } from "isbot"; +import { renderToPipeableStream } from "react-dom/server"; + +const ABORT_DELAY = 5_000; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return isbot(request.headers.get("user-agent") || "") + ? handleBotRequest( + request, + responseStatusCode, + responseHeaders, + remixContext, + ) + : handleBrowserRequest( + request, + responseStatusCode, + responseHeaders, + remixContext, + ); +} + +function handleBotRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + <RemixServer + context={remixContext} + url={request.url} + abortDelay={ABORT_DELAY} + />, + { + onAllReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + if (shellRendered) { + console.error(error); + } + }, + }, + ); + + setTimeout(abort, ABORT_DELAY); + }); +} + +function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + <RemixServer + context={remixContext} + url={request.url} + abortDelay={ABORT_DELAY} + />, + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + if (shellRendered) { + console.error(error); + } + }, + }, + ); + + setTimeout(abort, ABORT_DELAY); + }); +} diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx new file mode 100644 index 0000000..9768f45 --- /dev/null +++ b/frontend/app/root.tsx @@ -0,0 +1,35 @@ +import type { LinksFunction } from "@remix-run/node"; +import { + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from "@remix-run/react"; +import "./tailwind.css"; + +export const links: LinksFunction = () => { + return [{ rel: "icon", href: "/favicon.svg" }]; +}; + +export function Layout({ children }: { children: React.ReactNode }) { + return ( + <html lang="en"> + <head> + <meta charSet="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <Meta /> + <Links /> + </head> + <body> + {children} + <ScrollRestoration /> + <Scripts /> + </body> + </html> + ); +} + +export default function App() { + return <Outlet />; +} diff --git a/frontend/app/routes/_index.tsx b/frontend/app/routes/_index.tsx new file mode 100644 index 0000000..f39c7eb --- /dev/null +++ b/frontend/app/routes/_index.tsx @@ -0,0 +1,9 @@ +import type { MetaFunction } from "@remix-run/node"; + +export const meta: MetaFunction = () => { + return [{ title: "Albatross.swift" }]; +}; + +export default function Index() { + return <p>iOSDC 2024 Albatross.swift</p>; +} diff --git a/frontend/app/tailwind.css b/frontend/app/tailwind.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/frontend/app/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; |
