From c5bd2695f665aa1c6fb21873a73e1ccef61718c6 Mon Sep 17 00:00:00 2001 From: Matchu Date: Thu, 15 Sep 2022 03:01:56 -0700 Subject: [PATCH] A bit of SSR for the item page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Always been a bit annoyed to have even the item name load in so weird and slow 😅 this fixes it to come in much faster! This also allows us to SSR the item name in the page title, since we've put it in the GraphQL cache at SSR time! --- pages/items/[itemId]/index.tsx | 49 ++++++++++++++++++++++++++++++++++ pages/outfits/new.tsx | 8 +++--- src/app/ItemPage.js | 35 +++++++++++++----------- src/server/ssr-graphql.js | 4 +-- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/pages/items/[itemId]/index.tsx b/pages/items/[itemId]/index.tsx index 9b7971d..b0b06cf 100644 --- a/pages/items/[itemId]/index.tsx +++ b/pages/items/[itemId]/index.tsx @@ -1,7 +1,11 @@ +import { GetServerSideProps } from "next"; import ItemSearchPageToolbar from "../../../src/app/components/ItemSearchPageToolbar"; import ItemPage from "../../../src/app/ItemPage"; import PageLayout from "../../../src/app/PageLayout"; +import { gql, loadGraphqlQuery } from "../../../src/server/ssr-graphql"; import type { NextPageWithLayout } from "../../_app"; +// @ts-ignore doesn't understand module.exports +import { oneDay, oneWeek } from "../../../src/server/util"; const ItemPageWrapper: NextPageWithLayout = () => { return ; @@ -16,4 +20,49 @@ ItemPageWrapper.renderWithLayout = (children) => { ); }; +export const getServerSideProps: GetServerSideProps = async ({ + params, + res, +}) => { + if (params?.itemId == null) { + throw new Error(`assertion error: itemId param is missing`); + } + + // Load the most important, most stable item data to get onto the page ASAP. + // We'll cache it real hard, to help it load extra-fast for popular items! + const { errors, graphqlState } = await loadGraphqlQuery({ + query: gql` + query ItemsIndex_GetServerSideProps($itemId: ID!) { + item(id: $itemId) { + id + name + thumbnailUrl + description + isNc + isPb + createdAt + } + } + `, + variables: { itemId: params.itemId }, + }); + if (errors) { + console.warn( + `[SSR: /items/[itemId]] Skipping GraphQL preloading, got errors:` + ); + for (const error of errors) { + console.warn(`[SSR: /items/[itemId]]`, error); + } + return { props: { graphqlState: {} } }; + } + + // Cache this very aggressively, because it's such stable data! + res.setHeader( + "Cache-Control", + `public, s-maxage=${oneDay}, stale-while-revalidate=${oneWeek}` + ); + + return { props: { graphqlState } }; +}; + export default ItemPageWrapper; diff --git a/pages/outfits/new.tsx b/pages/outfits/new.tsx index fc9d019..84c85fc 100644 --- a/pages/outfits/new.tsx +++ b/pages/outfits/new.tsx @@ -19,7 +19,7 @@ export const getServerSideProps: GetServerSideProps = async ({ query }) => { // with it! We add it as a special `pageProps` key named `graphqlState`, // which the `App` component intercepts and gives to the Apollo client. const outfitState = readOutfitStateFromQuery(query); - const res = await loadGraphqlQuery({ + const { errors, graphqlState } = await loadGraphqlQuery({ query: gql` query OutfitsNew_GetServerSideProps( $speciesId: ID! @@ -49,17 +49,17 @@ export const getServerSideProps: GetServerSideProps = async ({ query }) => { wornItemIds: outfitState.wornItemIds, }, }); - if (res.errors) { + if (errors) { console.warn( `[SSR: /outfits/new] Skipping GraphQL preloading, got errors:` ); - for (const error of res.errors) { + for (const error of errors) { console.warn(`[SSR: /outfits/new]`, error); } return { props: { graphqlState: {} } }; } - return { props: { graphqlState: res.state } }; + return { props: { graphqlState } }; }; export default WardrobePageWrapper; diff --git a/src/app/ItemPage.js b/src/app/ItemPage.js index 8584b7b..bbed293 100644 --- a/src/app/ItemPage.js +++ b/src/app/ItemPage.js @@ -41,7 +41,6 @@ import { logAndCapture, MajorErrorMessage, useLocalStorage, - usePageTitle, } from "./util"; import HTML5Badge, { layerUsesHTML5 } from "./components/HTML5Badge"; import { @@ -59,6 +58,7 @@ import SpeciesFacesPicker, { colorIsBasic, } from "./ItemPage/SpeciesFacesPicker"; import { useRouter } from "next/router"; +import Head from "next/head"; function ItemPage() { const { query } = useRouter(); @@ -95,8 +95,6 @@ export function ItemPageContent({ itemId, isEmbedded = false }) { { variables: { itemId }, returnPartialData: true } ); - usePageTitle(data?.item?.name, { skip: isEmbedded }); - if (error) { return ; } @@ -104,19 +102,26 @@ export function ItemPageContent({ itemId, isEmbedded = false }) { const item = data?.item; return ( - - - - - - {isLoggedIn && } + <> + {!isEmbedded && item?.name && ( + + {item?.name} | Dress to Impress + + )} + + + + + + {isLoggedIn && } + + {!isEmbedded && } - {!isEmbedded && } - - + + ); } diff --git a/src/server/ssr-graphql.js b/src/server/ssr-graphql.js index d0391c1..c16117a 100644 --- a/src/server/ssr-graphql.js +++ b/src/server/ssr-graphql.js @@ -19,11 +19,11 @@ async function loadGraphqlQuery({ query, variables = {} }) { // a whole client!) const cache = new InMemoryCache(); cache.writeQuery({ query, variables, data }); - const state = cache.extract(); + const graphqlState = cache.extract(); // We return the data, errors, and cache state: we figure callers will almost // always want the errors and state, and may also want the data! - return { data, errors, state }; + return { data, errors, graphqlState }; } /**