Skip to content

Commit

Permalink
Merge pull request #2208 from iFixit/memoize-app-router-find-product
Browse files Browse the repository at this point in the history
Memoize app router find product
  • Loading branch information
masonmcelvain authored Jan 17, 2024
2 parents d72d7f5 + a2dac6e commit ce1ed13
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -1,36 +1,32 @@
import { DEFAULT_STORE_CODE, IFIXIT_ORIGIN } from '@config/env';
import { DEFAULT_STORE_CODE } from '@config/env';
import { getStoreList } from '@models/store';
import Product from '@pages/api/nextjs/cache/product';
import { ProductPageProps } from 'app/(defaultLayout)/products/app-router/[handle]/page';
import { devSandboxOrigin, shouldSkipCache } from 'app/_helpers/app-helpers';
import { findProduct } from 'app/_data/product';
import { shouldSkipCache } from 'app/_helpers/app-helpers';
import { StoreSelect } from '../../../store-select';
import { invariant } from '@ifixit/helpers';

export default async function ProductPageStoreSelect({
params,
searchParams,
}: ProductPageProps) {
const [stores, product] = await Promise.all([getStoreList(), getProduct()]);
const [stores, product] = await Promise.all([
getStoreList(),
findProduct({
handle: params.handle,
noCache: shouldSkipCache(searchParams),
}),
]);

return <StoreSelect stores={product ? storesWithProductUrls() : stores} />;

async function getProduct() {
const result = await Product.get(
{
handle: params.handle,
storeCode: DEFAULT_STORE_CODE,
ifixitOrigin: devSandboxOrigin() ?? IFIXIT_ORIGIN,
},
{ forceMiss: shouldSkipCache(searchParams) }
);
return result?.__typename === 'Product' ? result : null;
}

function storesWithProductUrls() {
invariant(product);
return stores.map((store) => {
if (productEnabledFor(store.code)) {
return {
...store,
url: new URL(`/products/${product!.handle}`, store.url).href,
url: new URL(`/products/${product.handle}`, store.url).href,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
shouldShowProductRating,
} from '@ifixit/helpers';
import type { Product } from '@models/product';
import { imagesFor } from 'app/_helpers/product-helpers';
import { defaultVariantIdFor, imagesFor } from 'app/_helpers/product-helpers';
import { jsonLdScriptProps } from 'react-schemaorg';
import type {
BreadcrumbList as BreadcrumbListLDSchema,
Expand Down Expand Up @@ -98,7 +98,11 @@ export function ProductJsonLDScript({
}

function variantUrl() {
return `${IFIXIT_ORIGIN}/products/${product.handle}?variant=${selectedVariantId}`;
let result = `${IFIXIT_ORIGIN}/products/${product.handle}`;
if (selectedVariantId !== defaultVariantIdFor(product)) {
result += `?variant=${selectedVariantId}`;
}
return result;
}

function oneYearFromNow() {
Expand Down
58 changes: 27 additions & 31 deletions frontend/app/(defaultLayout)/products/app-router/[handle]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { DEFAULT_STORE_CODE, IFIXIT_ORIGIN } from '@config/env';
import { IFIXIT_ORIGIN } from '@config/env';
import { flags } from '@config/flags';
import { invariant } from '@ifixit/helpers';
import Product, {
type Product as ProductType,
} from '@pages/api/nextjs/cache/product';
import { devSandboxOrigin, shouldSkipCache } from 'app/_helpers/app-helpers';
import { defaultVariantId, imagesFor } from 'app/_helpers/product-helpers';
import { type Product } from '@pages/api/nextjs/cache/product';
import { findProduct, findProductRedirect } from 'app/_data/product';
import { shouldSkipCache } from 'app/_helpers/app-helpers';
import { defaultVariantIdFor, imagesFor } from 'app/_helpers/product-helpers';
import type { Metadata } from 'next';
import { notFound, redirect } from 'next/navigation';
import {
Expand All @@ -29,27 +28,28 @@ export default async function ProductPage({
}: ProductPageProps) {
if (!flags.APP_ROUTER_PRODUCT_PAGE_ENABLED) notFound();

const data = await Product.get(
{
handle: params.handle,
storeCode: DEFAULT_STORE_CODE,
ifixitOrigin: devSandboxOrigin() ?? IFIXIT_ORIGIN,
},
{ forceMiss: shouldSkipCache(searchParams) }
);
const productRedirect = await findProductRedirect({
handle: params.handle,
noCache: shouldSkipCache(searchParams),
});

if (productRedirect != null) redirect(productRedirect.target);

if (data == null) notFound();
const product = await findProduct({
handle: params.handle,
noCache: shouldSkipCache(searchParams),
});

if (data.__typename === 'ProductRedirect') redirect(data.target);
if (product == null) notFound();

return (
<>
<ProductBreadcrumbsJsonLDScript product={data} />
<ProductBreadcrumbsJsonLDScript product={product} />
<ProductJsonLDScript
product={data}
selectedVariantId={selectedVariantId(data, searchParams)}
product={product}
selectedVariantId={selectedVariantId(product, searchParams)}
/>
<div>Product: {data.title}</div>
<div>Product: {product.title}</div>
</>
);
}
Expand All @@ -58,16 +58,12 @@ export async function generateMetadata({
params,
searchParams,
}: ProductPageProps): Promise<Metadata> {
const product = await Product.get(
{
handle: params.handle,
storeCode: DEFAULT_STORE_CODE,
ifixitOrigin: devSandboxOrigin() ?? IFIXIT_ORIGIN,
},
{ forceMiss: shouldSkipCache(searchParams) }
);
const product = await findProduct({
handle: params.handle,
noCache: shouldSkipCache(searchParams),
});

if (product == null || product.__typename !== 'Product') return {};
if (product == null) return {};

return {
title: product.metaTitle,
Expand Down Expand Up @@ -123,8 +119,8 @@ export async function generateMetadata({
}

function selectedVariantId(
product: ProductType,
product: Product,
searchParams: ProductPageProps['searchParams']
) {
return searchParams.variant ?? defaultVariantId(product);
return searchParams.variant ?? defaultVariantIdFor(product);
}
39 changes: 39 additions & 0 deletions frontend/app/_data/product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'server-only';

import { DEFAULT_STORE_CODE } from '@config/env';
import Product from '@pages/api/nextjs/cache/product';
import { ifixitOrigin } from 'app/_helpers/app-helpers';
import { cache } from 'react';

interface FindProductArgs {
handle: string;
noCache: boolean;
}

export async function findProduct({ handle, noCache }: FindProductArgs) {
const result = await findProductOrRedirect(handle, noCache);
if (result?.__typename === 'Product') return result;
return null;
}

export async function findProductRedirect({
handle,
noCache,
}: FindProductArgs) {
const result = await findProductOrRedirect(handle, noCache);
if (result?.__typename === 'ProductRedirect') return result;
return null;
}

const findProductOrRedirect = cache(
async (handle: string, skipCache: boolean) => {
return await Product.get(
{
handle,
storeCode: DEFAULT_STORE_CODE,
ifixitOrigin: ifixitOrigin(),
},
{ forceMiss: skipCache }
);
}
);
8 changes: 6 additions & 2 deletions frontend/app/_helpers/app-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CACHE_DISABLED } from '@config/env';
import { CACHE_DISABLED, IFIXIT_ORIGIN } from '@config/env';
import { headers } from 'next/headers';

export function shouldSkipCache(
Expand All @@ -7,7 +7,11 @@ export function shouldSkipCache(
return searchParams.disableCacheGets != null || CACHE_DISABLED;
}

export function devSandboxOrigin(): string | null {
export function ifixitOrigin(): string {
return devSandboxOrigin() ?? IFIXIT_ORIGIN;
}

function devSandboxOrigin(): string | null {
const host =
headers().get('x-ifixit-forwarded-host') ||
headers().get('x-forwarded-host');
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/_helpers/product-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function imagesFor(
return relevantImages.length > 0 ? relevantImages : product.fallbackImages;
}

export function defaultVariantId(product: Product): string {
export function defaultVariantIdFor(product: Product): string {
const variant =
product.variants.find(
(variant) => variant.quantityAvailable && variant.quantityAvailable > 0
Expand Down

2 comments on commit ce1ed13

@vercel
Copy link

@vercel vercel bot commented on ce1ed13 Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

react-commerce-prod – ./frontend

react-commerce-prod-ifixit.vercel.app
react-commerce-prod-git-main-ifixit.vercel.app
react-commerce-prod.vercel.app

@vercel
Copy link

@vercel vercel bot commented on ce1ed13 Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

react-commerce – ./frontend

react-commerce-git-main-ifixit.vercel.app
react-commerce-ifixit.vercel.app
react-commerce.vercel.app

Please sign in to comment.