From f84f2c6c0fcf0391ef8cb11c8de061cf90163aca Mon Sep 17 00:00:00 2001 From: Jimmy Briggs Date: Wed, 12 Jun 2024 18:02:26 -0400 Subject: [PATCH 1/4] config: abstract out application configuration constants --- app/app/config/constants.ts | 4 ++++ app/app/routes/_index.tsx | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 app/app/config/constants.ts diff --git a/app/app/config/constants.ts b/app/app/config/constants.ts new file mode 100644 index 0000000..f87ec55 --- /dev/null +++ b/app/app/config/constants.ts @@ -0,0 +1,4 @@ +export const appConfig = { + youtube_video_id: "fdi_z9NLT_w?si=iSeO4L8snTfPbG5H", + youtube_video_title: "Villard Bastien" +}; diff --git a/app/app/routes/_index.tsx b/app/app/routes/_index.tsx index 7c5c8fb..624be79 100644 --- a/app/app/routes/_index.tsx +++ b/app/app/routes/_index.tsx @@ -2,6 +2,7 @@ import {Header} from "../components/Header"; import {Footer} from "../components/Footer"; import YouTubeEmbed from "../components/YouTubeEmbed"; +import { appConfig } from "../config/constants"; // ASSETS // // Fonts @@ -17,8 +18,8 @@ import VillardImg from "../wp-content/uploads/sites/1302270/2022/06/Villard-S-Ba // Styles import "../styles/home.css"; -const YoutubeVideoId = "fdi_z9NLT_w?si=iSeO4L8snTfPbG5H"; -const YouTubeVideoTitle = "Villard Bastien" +const YoutubeVideoId = appConfig.youtube_video_id; +const YouTubeVideoTitle = appConfig.youtube_video_title; export default function Index() { return ( From 2292a46404cfcf7412d63cac0a8fe996637830f8 Mon Sep 17 00:00:00 2001 From: Jimmy Briggs Date: Wed, 12 Jun 2024 18:02:40 -0400 Subject: [PATCH 2/4] docs: add Docker section to app README --- app/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/README.md b/app/README.md index 1b4ae6e..65c3bb9 100644 --- a/app/README.md +++ b/app/README.md @@ -6,6 +6,13 @@ This template leverages [Remix SPA Mode](https://remix.run/docs/en/main/future/s 📖 See the [Remix Vite docs][remix-vite-docs] for details on supported features. +## Docker + +```shell +docker buildx build . -t bastienlaw:latest +docker run -d -p 8080 bastienlaw:latest +``` + ## Setup ```shellscript From d8c70f4bb4a5e47c8c2a86008b50d4c01eb7f0b6 Mon Sep 17 00:00:00 2001 From: Patrick Howard Date: Thu, 13 Jun 2024 17:46:51 -0400 Subject: [PATCH 3/4] add `catchall` route handler in `$.tsx` route file --- app/app/routes/$.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 app/app/routes/$.tsx diff --git a/app/app/routes/$.tsx b/app/app/routes/$.tsx new file mode 100644 index 0000000..3c778e2 --- /dev/null +++ b/app/app/routes/$.tsx @@ -0,0 +1,10 @@ +// This is a 'catch-all' route that will match any URL that hasn't already been matched by another route. +// Navigate to the Home (index) route to see this route in action. + +import { redirect } from "@remix-run/react"; + +// Provide a `loader` function to handle the data fetching for this route. +export const loader = async () => { + // Redirect to the home page + return redirect('/'); +}; From 83da898f031ea80a3afcccd2e667134e8095201f Mon Sep 17 00:00:00 2001 From: Jimmy Briggs Date: Thu, 3 Oct 2024 14:28:22 -0400 Subject: [PATCH 4/4] feat: add gtags support --- app/app/entry.server.tsx | 160 +- app/app/root.tsx | 52 +- app/app/utils/gtags.client.ts | 48 + app/package-lock.json | 13281 ++++++++++++++++++++++++++++++++ app/package.json | 11 +- app/pnpm-lock.yaml | 8471 -------------------- app/vite.config.ts | 96 +- 7 files changed, 13555 insertions(+), 8564 deletions(-) create mode 100644 app/app/utils/gtags.client.ts create mode 100644 app/package-lock.json delete mode 100644 app/pnpm-lock.yaml diff --git a/app/app/entry.server.tsx b/app/app/entry.server.tsx index fc66fc8..c2c07d4 100644 --- a/app/app/entry.server.tsx +++ b/app/app/entry.server.tsx @@ -1,19 +1,159 @@ -import type { EntryContext } from "@remix-run/node"; +import { PassThrough } from "node:stream"; + +import type { AppLoadContext, EntryContext } from "@remix-run/node"; +import { createReadableStreamFromReadable } from "@remix-run/node"; import { RemixServer } from "@remix-run/react"; -import { renderToString } from "react-dom/server"; +import { config } from "dotenv"; +import * as isbotModule from "isbot"; +import { renderToPipeableStream } from "react-dom/server"; + +// Load .env variables +config(); + +const ABORT_DELAY = 5_000; export default function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, - remixContext: EntryContext + remixContext: EntryContext, + loadContext: AppLoadContext, +) { + const prohibitOutOfOrderStreaming = + isBotRequest(request.headers.get("user-agent")) || remixContext.isSpaMode; + + return prohibitOutOfOrderStreaming + ? handleBotRequest( + request, + responseStatusCode, + responseHeaders, + remixContext, + ) + : handleBrowserRequest( + request, + responseStatusCode, + responseHeaders, + remixContext, + ); +} + +// We have some Remix apps in the wild already running with isbot@3 so we need +// to maintain backwards compatibility even though we want new apps to use +// isbot@4. That way, we can ship this as a minor Semver update to @remix-run/dev. +function isBotRequest(userAgent: string | null) { + if (!userAgent) { + return false; + } + + // isbot >= 3.8.0, >4 + if ("isbot" in isbotModule && typeof isbotModule.isbot === "function") { + return isbotModule.isbot(userAgent); + } + + // isbot < 3.8.0 + if ("default" in isbotModule && typeof isbotModule.default === "function") { + return isbotModule.default(userAgent); + } + + return false; +} + +function handleBotRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, ) { - let html = renderToString( - - ); - html = "\n" + html; - return new Response(html, { - headers: { "Content-Type": "text/html" }, - status: responseStatusCode, + 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/app/root.tsx b/app/app/root.tsx index 82846dc..c19ef63 100644 --- a/app/app/root.tsx +++ b/app/app/root.tsx @@ -1,20 +1,32 @@ +import { json } from "@remix-run/node"; + import { + Link, Links, Meta, MetaFunction, Outlet, Scripts, ScrollRestoration, + useLoaderData, + useLocation } from "@remix-run/react"; +import { useEffect } from "react"; + +import * as gtag from "./utils/gtags.client"; + import "./styles/typography.css"; import './styles/global.css'; -import ScrollToTopButton from "./components/ScrollToTopButton"; -// import "./styles/dsethtml.css" -// import "./styles/dsethtml2.css" +import ScrollToTopButton from "./components/ScrollToTopButton"; // import ChatbotScript from "./components/ChatBot"; +// Load the GA tracking id from the .env +export const loader = async () => { + return json({ gaTrackingId: "G-BQBBSBFGZG" }); +}; + export const meta: MetaFunction = () => { return [ {title: "Bastien Law"}, @@ -22,7 +34,16 @@ export const meta: MetaFunction = () => { ]; }; -export function Layout({children}: {children: React.ReactNode}) { +export function Layout({ children }: { children: React.ReactNode }) { + const location = useLocation(); + const gaTrackingId = "G-BQBBSBFGZG"; + + useEffect(() => { + if (gaTrackingId?.length) { + gtag.pageview(location.pathname, gaTrackingId); + } + }, [location, gaTrackingId]); + return ( @@ -32,6 +53,29 @@ export function Layout({children}: {children: React.ReactNode}) { + {process.env.NODE_ENV === "development" || !gaTrackingId ? null : ( + <> +