From 037efa682605b1d37b4de8ecd18b5cbf4f9a6e16 Mon Sep 17 00:00:00 2001 From: Mukesh Singh Date: Thu, 24 Oct 2024 10:21:31 +0530 Subject: [PATCH] feat: meta tags and other improvements for spa --- app/entry.client.nonSpa.tsx | 18 +++++ app/entry.client.tsx | 24 +++--- app/entry.server.nonSpa.tsx | 105 +++++++++++++++++++++++++ app/entry.server.tsx | 148 ++++-------------------------------- app/index.html | 56 ++++++++++++++ app/root.tsx | 9 +++ 6 files changed, 210 insertions(+), 150 deletions(-) create mode 100644 app/entry.client.nonSpa.tsx create mode 100644 app/entry.server.nonSpa.tsx create mode 100644 app/index.html diff --git a/app/entry.client.nonSpa.tsx b/app/entry.client.nonSpa.tsx new file mode 100644 index 0000000..ae1c09b --- /dev/null +++ b/app/entry.client.nonSpa.tsx @@ -0,0 +1,18 @@ +/** + * By default, Remix will handle hydrating your app on the client for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.client + */ + +import { startTransition, StrictMode } from 'react'; +import { RemixBrowser } from '@remix-run/react'; +import { hydrateRoot } from 'react-dom/client'; + +startTransition(() => { + hydrateRoot( + document, + + + + ); +}); diff --git a/app/entry.client.tsx b/app/entry.client.tsx index 94d5dc0..deba21c 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -1,18 +1,12 @@ -/** - * By default, Remix will handle hydrating your app on the client for you. - * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ - * For more information, see https://remix.run/file-conventions/entry.client - */ - -import { RemixBrowser } from "@remix-run/react"; -import { startTransition, StrictMode } from "react"; -import { hydrateRoot } from "react-dom/client"; +import { startTransition, StrictMode } from 'react'; +import { RemixBrowser } from '@remix-run/react'; +import { hydrateRoot } from 'react-dom/client'; startTransition(() => { - hydrateRoot( - document, - - - - ); + hydrateRoot( + document.querySelector('#app')!, + + + + ); }); diff --git a/app/entry.server.nonSpa.tsx b/app/entry.server.nonSpa.tsx new file mode 100644 index 0000000..288b912 --- /dev/null +++ b/app/entry.server.nonSpa.tsx @@ -0,0 +1,105 @@ +/** + * By default, Remix will handle generating the HTTP Response for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.server + */ + +import type { AppLoadContext, EntryContext } from '@remix-run/node'; +import { createReadableStreamFromReadable } from '@remix-run/node'; +import { RemixServer } from '@remix-run/react'; +import { isbot } from 'isbot'; +import { PassThrough } from 'node:stream'; +import { renderToPipeableStream } from 'react-dom/server'; + +const ABORT_DELAY = 5_000; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + // This is ignored so we can keep it in the template for visibility. Feel + // free to delete this parameter in your app if you're not using it! + // eslint-disable-next-line @typescript-eslint/no-unused-vars + loadContext: AppLoadContext +) { + 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(, { + 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; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + 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(, { + 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; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + }); + + setTimeout(abort, ABORT_DELAY); + }); +} diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 45db322..079c22b 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -1,140 +1,18 @@ -/** - * By default, Remix will handle generating the HTTP Response for you. - * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ - * For more information, see https://remix.run/file-conventions/entry.server - */ +import type { EntryContext } from '@remix-run/node'; +import { RemixServer } from '@remix-run/react'; +import fs from 'node:fs'; +import path from 'node:path'; +import { renderToString } from 'react-dom/server'; -import { PassThrough } from "node:stream"; +export default function handleRequest(request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext) { + const shellHtml = fs.readFileSync(path.join(process.cwd(), 'app/index.html')).toString(); -import type { AppLoadContext, 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 appHtml = renderToString(); -const ABORT_DELAY = 5_000; + const html = shellHtml.replace('', appHtml); -export default function handleRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext, - // This is ignored so we can keep it in the template for visibility. Feel - // free to delete this parameter in your app if you're not using it! - // eslint-disable-next-line @typescript-eslint/no-unused-vars - loadContext: AppLoadContext -) { - 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( - , - { - 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; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - 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( - , - { - 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; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - if (shellRendered) { - console.error(error); - } - }, - } - ); - - setTimeout(abort, ABORT_DELAY); - }); + return new Response(html, { + headers: { 'Content-Type': 'text/html' }, + status: responseStatusCode, + }); } diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..4d269c2 --- /dev/null +++ b/app/index.html @@ -0,0 +1,56 @@ + + + + My Cool App! + + Shield + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/app/root.tsx b/app/root.tsx index 3a6dcdb..90f334e 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -190,3 +190,12 @@ export function App() { ); } + +export function HydrateFallback() { + return ( + <> +

Loading...

+ + + ); +}