A bit of SSR for the item page

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!
This commit is contained in:
Emi Matchu 2022-09-15 03:01:56 -07:00
parent 2887d952de
commit c5bd2695f6
4 changed files with 75 additions and 21 deletions

View file

@ -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 <ItemPage />;
@ -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;

View file

@ -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;

View file

@ -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 <MajorErrorMessage error={error} />;
}
@ -104,19 +102,26 @@ export function ItemPageContent({ itemId, isEmbedded = false }) {
const item = data?.item;
return (
<ItemPageLayout item={item} isEmbedded={isEmbedded}>
<VStack spacing="8" marginTop="4">
<ItemPageDescription
description={item?.description}
isEmbedded={isEmbedded}
/>
<VStack spacing="4">
<ItemPageTradeLinks itemId={itemId} isEmbedded={isEmbedded} />
{isLoggedIn && <ItemPageOwnWantButtons itemId={itemId} />}
<>
{!isEmbedded && item?.name && (
<Head>
<title>{item?.name} | Dress to Impress</title>
</Head>
)}
<ItemPageLayout item={item} isEmbedded={isEmbedded}>
<VStack spacing="8" marginTop="4">
<ItemPageDescription
description={item?.description}
isEmbedded={isEmbedded}
/>
<VStack spacing="4">
<ItemPageTradeLinks itemId={itemId} isEmbedded={isEmbedded} />
{isLoggedIn && <ItemPageOwnWantButtons itemId={itemId} />}
</VStack>
{!isEmbedded && <ItemPageOutfitPreview itemId={itemId} />}
</VStack>
{!isEmbedded && <ItemPageOutfitPreview itemId={itemId} />}
</VStack>
</ItemPageLayout>
</ItemPageLayout>
</>
);
}

View file

@ -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 };
}
/**