From e6dcfd0b3069763ac893db65e1db2ad887dbf975 Mon Sep 17 00:00:00 2001 From: James Vansteenkiste Date: Wed, 22 Feb 2023 13:59:02 -0500 Subject: [PATCH] add: csp, nonce & defer --- app/components/NonceContext.tsx | 3 +++ app/entry.server.tsx | 17 +++++++++++++++-- app/root.tsx | 10 +++++++--- app/routes/defer.tsx | 31 +++++++++++++++++++++++++++++++ app/routes/index.tsx | 28 +++------------------------- 5 files changed, 59 insertions(+), 30 deletions(-) create mode 100644 app/components/NonceContext.tsx create mode 100644 app/routes/defer.tsx diff --git a/app/components/NonceContext.tsx b/app/components/NonceContext.tsx new file mode 100644 index 0000000..e802360 --- /dev/null +++ b/app/components/NonceContext.tsx @@ -0,0 +1,3 @@ +import React from 'react'; + +export const NonceContext = React.createContext(undefined); diff --git a/app/entry.server.tsx b/app/entry.server.tsx index d349f8e..a881bf9 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -5,6 +5,11 @@ import { RemixServer } from "@remix-run/react"; import isbot from "isbot"; import { renderToPipeableStream } from "react-dom/server"; +import { NonceContext } from "./components/NonceContext"; + +// In real applications you would generate a new nonce for each request. +const NONCE = "sample-nonce"; + const ABORT_DELAY = 5000; export default function handleRequest( @@ -13,6 +18,8 @@ export default function handleRequest( responseHeaders: Headers, remixContext: EntryContext ) { + responseHeaders.set('Content-Security-Policy', `script-src 'self' 'nonce-${NONCE}';`) + return isbot(request.headers.get("user-agent")) ? handleBotRequest( request, @@ -38,8 +45,11 @@ function handleBotRequest( let didError = false; const { pipe, abort } = renderToPipeableStream( - , + + + , { + nonce: NONCE, onAllReady() { const body = new PassThrough(); @@ -79,8 +89,11 @@ function handleBrowserRequest( let didError = false; const { pipe, abort } = renderToPipeableStream( - , + + + , { + nonce: NONCE, onShellReady() { const body = new PassThrough(); diff --git a/app/root.tsx b/app/root.tsx index 927a0f7..648766b 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -7,6 +7,8 @@ import { Scripts, ScrollRestoration, } from "@remix-run/react"; +import { useContext } from "react"; +import { NonceContext } from "./components/NonceContext"; export const meta: MetaFunction = () => ({ charset: "utf-8", @@ -15,6 +17,8 @@ export const meta: MetaFunction = () => ({ }); export default function App() { + const nonce = useContext(NonceContext); + return ( @@ -23,9 +27,9 @@ export default function App() { - - - + + + ); diff --git a/app/routes/defer.tsx b/app/routes/defer.tsx new file mode 100644 index 0000000..a08bb8e --- /dev/null +++ b/app/routes/defer.tsx @@ -0,0 +1,31 @@ +import { defer } from "@remix-run/node"; +import { Await, useLoaderData } from "@remix-run/react"; +import { Suspense } from "react"; + +export const loader = () => { + return defer({ + critical: "data", + slowPromise: new Promise((resolve) => setTimeout(() => resolve('Slow promise content'), 1000)), + }); +}; + +export default function Defer() { + const data = useLoaderData(); + + return ( +
+ Loading deffered content

}> + Error loading deffered content!

} + > + {(content) => ( +

+ {content} +

+ )} +
+
+
+ ); +} diff --git a/app/routes/index.tsx b/app/routes/index.tsx index cbca612..54ac4de 100644 --- a/app/routes/index.tsx +++ b/app/routes/index.tsx @@ -1,32 +1,10 @@ +import { Link } from "@remix-run/react"; + export default function Index() { return ( ); }