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:
parent
2887d952de
commit
c5bd2695f6
4 changed files with 75 additions and 21 deletions
|
@ -1,7 +1,11 @@
|
||||||
|
import { GetServerSideProps } from "next";
|
||||||
import ItemSearchPageToolbar from "../../../src/app/components/ItemSearchPageToolbar";
|
import ItemSearchPageToolbar from "../../../src/app/components/ItemSearchPageToolbar";
|
||||||
import ItemPage from "../../../src/app/ItemPage";
|
import ItemPage from "../../../src/app/ItemPage";
|
||||||
import PageLayout from "../../../src/app/PageLayout";
|
import PageLayout from "../../../src/app/PageLayout";
|
||||||
|
import { gql, loadGraphqlQuery } from "../../../src/server/ssr-graphql";
|
||||||
import type { NextPageWithLayout } from "../../_app";
|
import type { NextPageWithLayout } from "../../_app";
|
||||||
|
// @ts-ignore doesn't understand module.exports
|
||||||
|
import { oneDay, oneWeek } from "../../../src/server/util";
|
||||||
|
|
||||||
const ItemPageWrapper: NextPageWithLayout = () => {
|
const ItemPageWrapper: NextPageWithLayout = () => {
|
||||||
return <ItemPage />;
|
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;
|
export default ItemPageWrapper;
|
||||||
|
|
|
@ -19,7 +19,7 @@ export const getServerSideProps: GetServerSideProps = async ({ query }) => {
|
||||||
// with it! We add it as a special `pageProps` key named `graphqlState`,
|
// with it! We add it as a special `pageProps` key named `graphqlState`,
|
||||||
// which the `App` component intercepts and gives to the Apollo client.
|
// which the `App` component intercepts and gives to the Apollo client.
|
||||||
const outfitState = readOutfitStateFromQuery(query);
|
const outfitState = readOutfitStateFromQuery(query);
|
||||||
const res = await loadGraphqlQuery({
|
const { errors, graphqlState } = await loadGraphqlQuery({
|
||||||
query: gql`
|
query: gql`
|
||||||
query OutfitsNew_GetServerSideProps(
|
query OutfitsNew_GetServerSideProps(
|
||||||
$speciesId: ID!
|
$speciesId: ID!
|
||||||
|
@ -49,17 +49,17 @@ export const getServerSideProps: GetServerSideProps = async ({ query }) => {
|
||||||
wornItemIds: outfitState.wornItemIds,
|
wornItemIds: outfitState.wornItemIds,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (res.errors) {
|
if (errors) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`[SSR: /outfits/new] Skipping GraphQL preloading, got errors:`
|
`[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);
|
console.warn(`[SSR: /outfits/new]`, error);
|
||||||
}
|
}
|
||||||
return { props: { graphqlState: {} } };
|
return { props: { graphqlState: {} } };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { props: { graphqlState: res.state } };
|
return { props: { graphqlState } };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WardrobePageWrapper;
|
export default WardrobePageWrapper;
|
||||||
|
|
|
@ -41,7 +41,6 @@ import {
|
||||||
logAndCapture,
|
logAndCapture,
|
||||||
MajorErrorMessage,
|
MajorErrorMessage,
|
||||||
useLocalStorage,
|
useLocalStorage,
|
||||||
usePageTitle,
|
|
||||||
} from "./util";
|
} from "./util";
|
||||||
import HTML5Badge, { layerUsesHTML5 } from "./components/HTML5Badge";
|
import HTML5Badge, { layerUsesHTML5 } from "./components/HTML5Badge";
|
||||||
import {
|
import {
|
||||||
|
@ -59,6 +58,7 @@ import SpeciesFacesPicker, {
|
||||||
colorIsBasic,
|
colorIsBasic,
|
||||||
} from "./ItemPage/SpeciesFacesPicker";
|
} from "./ItemPage/SpeciesFacesPicker";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import Head from "next/head";
|
||||||
|
|
||||||
function ItemPage() {
|
function ItemPage() {
|
||||||
const { query } = useRouter();
|
const { query } = useRouter();
|
||||||
|
@ -95,8 +95,6 @@ export function ItemPageContent({ itemId, isEmbedded = false }) {
|
||||||
{ variables: { itemId }, returnPartialData: true }
|
{ variables: { itemId }, returnPartialData: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
usePageTitle(data?.item?.name, { skip: isEmbedded });
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <MajorErrorMessage error={error} />;
|
return <MajorErrorMessage error={error} />;
|
||||||
}
|
}
|
||||||
|
@ -104,6 +102,12 @@ export function ItemPageContent({ itemId, isEmbedded = false }) {
|
||||||
const item = data?.item;
|
const item = data?.item;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{!isEmbedded && item?.name && (
|
||||||
|
<Head>
|
||||||
|
<title>{item?.name} | Dress to Impress</title>
|
||||||
|
</Head>
|
||||||
|
)}
|
||||||
<ItemPageLayout item={item} isEmbedded={isEmbedded}>
|
<ItemPageLayout item={item} isEmbedded={isEmbedded}>
|
||||||
<VStack spacing="8" marginTop="4">
|
<VStack spacing="8" marginTop="4">
|
||||||
<ItemPageDescription
|
<ItemPageDescription
|
||||||
|
@ -117,6 +121,7 @@ export function ItemPageContent({ itemId, isEmbedded = false }) {
|
||||||
{!isEmbedded && <ItemPageOutfitPreview itemId={itemId} />}
|
{!isEmbedded && <ItemPageOutfitPreview itemId={itemId} />}
|
||||||
</VStack>
|
</VStack>
|
||||||
</ItemPageLayout>
|
</ItemPageLayout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,11 @@ async function loadGraphqlQuery({ query, variables = {} }) {
|
||||||
// a whole client!)
|
// a whole client!)
|
||||||
const cache = new InMemoryCache();
|
const cache = new InMemoryCache();
|
||||||
cache.writeQuery({ query, variables, data });
|
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
|
// 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!
|
// always want the errors and state, and may also want the data!
|
||||||
return { data, errors, state };
|
return { data, errors, graphqlState };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue