Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/app router #213

Merged
merged 21 commits into from
Dec 6, 2023
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ To cache our server-rendered pages at the Fastly layer, we use response headers
1. `Surrogate-Control` response header needs to be added to pages where caching is desired ([reference](https://docs.fastly.com/en/guides/working-with-surrogate-keys)),
2. `Surrogate-Key` response header needs to be added to enable appropriate cache invalidation ([reference](https://developer.fastly.com/reference/api/purging/)).

On the Next.js side we'll need to include a few primary response headers to then control caching (in our case, we're setting these headers from `getServerSideProps` on server-rendered pages that we'd like to cache).
On the Next.js side we'll need to include a few primary response headers to then control caching (in our case, we're setting these headers from `middleware` on server-rendered pages that we'd like to cache).

- `surrogate-control` – Fastly-specific header used to set the cache policies. (`max-age`, `stale-while-revalidate`, `stale-while-error`).
- `surrogate-key` – Fastly-specific header that allows purging by key. Note: this header is removed by Fastly before sending the response to the client. To see the value of this header, you must include the [`Fastly-Debug`](https://developer.fastly.com/reference/http/http-headers/Fastly-Debug/) header in your request.
Expand Down
5 changes: 5 additions & 0 deletions packages/nextjs/app/about/page.tsx
Copy link
Member Author

Choose a reason for hiding this comment

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

From migration guide -- create basic routes and put old pages as client components

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import AboutPage from "app/migration/about";

export default async function Page() {
return <AboutPage />;
}
19 changes: 19 additions & 0 deletions packages/nextjs/app/categories/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import CategoriesPage from "app/migration/categories";
import { getAllCategories } from "utils/getAllCategoriesQuery";
import { isString, pluralize } from "utils/pluralize";

const getData = async () => {
const categories = await getAllCategories();
const categoryNames = pluralize((categories || []).map((cat) => cat.name).filter(isString));

return {
categories,
categoryNames,
};
};

export default async function Page() {
const data = await getData();

return <CategoriesPage {...data} />;
}
84 changes: 83 additions & 1 deletion packages/nextjs/app/global.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,85 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
margin: 0;
@apply bg-secondary;
}

/* Cabinet Grotesk */
/* See: https://api.fontshare.com/v2/css?f[]=cabinet-grotesk@500,700,400,300&display=swap */
/* See: https://nextjs.org/docs/app/building-your-application/styling/css-modules#external-stylesheets */
@font-face {
font-family: 'Cabinet Grotesk';
src: url('//cdn.fontshare.com/wf/7GWNQ5AHAZORLOWZ7ELKPLOIQITAR5S5/NYLYMGXMB4RANWVNJSIHG2IKPZ44CN5E/MT4CWVHB3N2C6KFUZ75QK4JQ2FYK4J4M.woff2') format('woff2'),
url('//cdn.fontshare.com/wf/7GWNQ5AHAZORLOWZ7ELKPLOIQITAR5S5/NYLYMGXMB4RANWVNJSIHG2IKPZ44CN5E/MT4CWVHB3N2C6KFUZ75QK4JQ2FYK4J4M.woff') format('woff'),
url('//cdn.fontshare.com/wf/7GWNQ5AHAZORLOWZ7ELKPLOIQITAR5S5/NYLYMGXMB4RANWVNJSIHG2IKPZ44CN5E/MT4CWVHB3N2C6KFUZ75QK4JQ2FYK4J4M.ttf') format('truetype');
font-weight: 300;
font-display: swap;
font-style: normal;
}

@font-face {
font-family: 'Cabinet Grotesk';
src: url('//cdn.fontshare.com/wf/J6PPRPKWXDUIYA47IXLEQB4R4OPVYDQH/N2ZXAXWEHVMLISD2TIXJC7EF4GOY43L4/NXM4Z4TDCMYWBZ7AVI2N6DQ5VMWNENMU.woff2') format('woff2'),
url('//cdn.fontshare.com/wf/J6PPRPKWXDUIYA47IXLEQB4R4OPVYDQH/N2ZXAXWEHVMLISD2TIXJC7EF4GOY43L4/NXM4Z4TDCMYWBZ7AVI2N6DQ5VMWNENMU.woff') format('woff'),
url('//cdn.fontshare.com/wf/J6PPRPKWXDUIYA47IXLEQB4R4OPVYDQH/N2ZXAXWEHVMLISD2TIXJC7EF4GOY43L4/NXM4Z4TDCMYWBZ7AVI2N6DQ5VMWNENMU.ttf') format('truetype');
font-weight: 400;
font-display: swap;
font-style: normal;
}

@font-face {
font-family: 'Cabinet Grotesk';
src: url('//cdn.fontshare.com/wf/CKQBK2QBTCDREE7L3MXZ3PPW7LDNJCWU/OTOY7FQFSFOJVZKJWKO2EHUJLOGBDN4Q/4CO2ETY7NITKLUDKMYJ75RHJSPHOJ7XT.woff2') format('woff2'),
url('//cdn.fontshare.com/wf/CKQBK2QBTCDREE7L3MXZ3PPW7LDNJCWU/OTOY7FQFSFOJVZKJWKO2EHUJLOGBDN4Q/4CO2ETY7NITKLUDKMYJ75RHJSPHOJ7XT.woff') format('woff'),
url('//cdn.fontshare.com/wf/CKQBK2QBTCDREE7L3MXZ3PPW7LDNJCWU/OTOY7FQFSFOJVZKJWKO2EHUJLOGBDN4Q/4CO2ETY7NITKLUDKMYJ75RHJSPHOJ7XT.ttf') format('truetype');
font-weight: 500;
font-display: swap;
font-style: normal;
}

@font-face {
font-family: 'Cabinet Grotesk';
src: url('//cdn.fontshare.com/wf/XMXWOHABYLQDJ42L65EFRYNVRY37HQCB/B2O4O6V3JMFM2WDCYQI3A47L5U4THDUL/WN5274VQ3AUBDFP74GB4EC4XYJ3EKVNE.woff2') format('woff2'),
url('//cdn.fontshare.com/wf/XMXWOHABYLQDJ42L65EFRYNVRY37HQCB/B2O4O6V3JMFM2WDCYQI3A47L5U4THDUL/WN5274VQ3AUBDFP74GB4EC4XYJ3EKVNE.woff') format('woff'),
url('//cdn.fontshare.com/wf/XMXWOHABYLQDJ42L65EFRYNVRY37HQCB/B2O4O6V3JMFM2WDCYQI3A47L5U4THDUL/WN5274VQ3AUBDFP74GB4EC4XYJ3EKVNE.ttf') format('truetype');
font-weight: 700;
font-display: swap;
font-style: normal;
}


@font-face {
font-family: "JeanLuc";
src: url('../assets/fonts/jeanluc/jeanlucweb-bold.woff');
font-weight: bold;
font-style: normal;
}

@font-face {
font-family: "JeanLuc";
src: url('../assets/fonts/jeanluc/jeanlucweb-thin.woff');
font-weight: auto;
font-style: normal;
}

@font-face {
font-family: "JetBrains Mono";
src: url('../assets/fonts/jetbrainsmono/JetBrainsMono-Bold.woff2');
font-weight: bold;
font-style: normal;
}

@font-face {
font-family: "JetBrains Mono";
src: url('../assets/fonts/jetbrainsmono/JetBrainsMono-Regular.woff2');
font-weight: auto;
font-style: normal;
}

code {
@apply rounded-md bg-black/5 px-1.5 py-0.5;
font-family: 'JetBrains Mono', monospace;
font-size: 0.9rem;
}
32 changes: 31 additions & 1 deletion packages/nextjs/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,39 @@
import { Footer, MobileNavProvider } from "./ui/shared-ui";
import "./global.css";
import { Header } from "components/Header/Header";
import { Metadata } from "next";
import { CartProvider } from "components/CartContext";
import { AnimatePresence, MotionConfig } from "./ui/framer";

export const metadata: Metadata = {
title: "Home",
description: "Welcome to Next.js",
};

export const viewport = {
width: "device-width",
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
<body>
<MotionConfig reducedMotion="user">
<CartProvider>
<div className="min-h-screen flex flex-col">
<MobileNavProvider>
<Header />
</MobileNavProvider>
<main className="flex-1">
<AnimatePresence initial={false} mode="wait">
{children}
</AnimatePresence>
</main>
<Footer />
</div>
</CartProvider>
</MotionConfig>
</body>
</html>
);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

from migration guide -- old pages are now client side components. Future work entails moving client components to leaf nodes of component tree

Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import * as React from "react";
import { NextPage } from "next";
import NextImage from "next/legacy/image";
Expand Down Expand Up @@ -58,7 +60,7 @@ const AboutPage: NextPage = () => {
</div>
<div className="order-2">
<NextImage
src={require("../../../docs/img/big-picture.png")}
src={require("../../../../docs/img/big-picture.png")}
loader={localImageLoader}
layout="intrinsic"
width={DIAGRAM_WIDTH}
Expand Down Expand Up @@ -160,7 +162,7 @@ const AboutPage: NextPage = () => {
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-9 text-primary">
<NextImage
src={require("../../../docs/img/caching-diagram.png")}
src={require("../../../../docs/img/caching-diagram.png")}
loader={localImageLoader}
layout="intrinsic"
width={FASTLY_WIDTH}
Expand Down Expand Up @@ -215,8 +217,8 @@ const AboutPage: NextPage = () => {
</div>
<div className="mt-3">
On the Next.js side we’ll need to include a few primary response headers to then control caching (in
our case, we’re setting these headers from <code>getServerSideProps</code> on server-rendered pages
that we’d like to cache).
our case, we’re setting these headers from <code>middleware</code> on server-rendered pages that
we’d like to cache).
<ul>
<li className="my-3 last-of-type:mb-0 flex items-baseline gap-2">
<div className="top-1 relative">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { GetServerSideProps, NextPage } from "next";
"use client";

import { NextPage } from "next";

import { WeDontSellBreadBanner } from "shared-ui";
import { setCachingHeaders } from "utils/setCachingHeaders";
import { SanityType } from "utils/consts";
import { isString, pluralize } from "utils/pluralize";
import { getAllCategories } from "utils/getAllCategoriesQuery";

import { CategoryList } from "components/CategoryList";
import { PageHead } from "components/PageHead";
Expand Down Expand Up @@ -34,18 +32,4 @@ const CategoriesPage: NextPage<PageProps> = ({ categories, categoryNames }) => {
);
};

export const getServerSideProps = (async ({ res }) => {
setCachingHeaders(res, [SanityType.Category, SanityType.CategoryImage]);

const categories = await getAllCategories();
const categoryNames = pluralize((categories || []).map((cat) => cat.name).filter(isString));

return {
props: {
categories,
categoryNames,
},
};
}) satisfies GetServerSideProps<PageProps>;

export default CategoriesPage;
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { MdArrowForward } from "react-icons/md";
import { Button, Input, Pill, Checkbox, Select, LinkText } from "shared-ui";

Expand Down Expand Up @@ -36,6 +38,7 @@ export default function ComponentsPage() {
<h1 className="text-h4">Select</h1>
<div className="mb-8 mt-2">
<Select
id="demo"
label="Label"
placeholder="Placeholder Text"
options={[
Expand Down
22 changes: 22 additions & 0 deletions packages/nextjs/app/migration/getOrderingFromQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// TODO: This file can be imported from shared-ui once the server/client split is resolved

import { ParsedUrlQuery } from "querystring";
import { SORT_OPTIONS, SORT_QUERY_PARAM } from "./sorting";

export const getOrderingFromQuery = (query: ParsedUrlQuery) => {
const { [SORT_QUERY_PARAM]: sortValue } = query;

// Sort/ordering
let ordering = SORT_OPTIONS.default.ordering;
if (sortValue) {
// If sort is string[], use first item
// (e.g. User modified url, wouldn't happen normally)
const sortType = Array.isArray(sortValue) ? sortValue[0] : sortValue;
const sortOption = SORT_OPTIONS[sortType];
if (sortOption?.ordering) {
ordering = sortOption.ordering;
}
}

return ordering;
};
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
"use client";

import type { Categories, Products } from "utils/groqTypes/ProductList";
import * as React from "react";
import { GetServerSideProps, NextPage } from "next";
import { NextPage } from "next";
import { FiArrowRight } from "react-icons/fi";
import Link from "next/link";
import NextImage from "next/legacy/image";

import { Button, FeaturedQuote } from "shared-ui";
import { setCachingHeaders } from "utils/setCachingHeaders";
import { Button, FeaturedQuote } from "../ui/shared-ui";
import { localImageLoader } from "utils/localImageLoader";
import { SanityType } from "utils/consts";
import { getAllCategories } from "utils/getAllCategoriesQuery";
import { getRecommendations } from "utils/getRecommendationsQuery";

import featuredImg from "assets/featured-story.jpg";
import { FeaturedList } from "components/FeaturedList";
import { Image } from "components/Image";
import { PageHead } from "components/PageHead";

interface PageProps {
data?: {
Expand All @@ -27,10 +24,6 @@ interface PageProps {
const Home: NextPage<PageProps> = ({ data }) => {
return (
<>
<PageHead
title="Home"
description="Formidable Boulangerie home page. A showcase of Next.js, Sanity CMS, and Fastly CDN."
/>
<section className="container">
<div className="flex justify-between items-center py-9">
<div className="max-w-[600px]">
Expand Down Expand Up @@ -101,20 +94,4 @@ const TitleBanner = ({ children }: React.PropsWithChildren) => (
</div>
);

export const getServerSideProps = (async ({ res }) => {
setCachingHeaders(res, [SanityType.Category, SanityType.CategoryImage]);

const categories = await getAllCategories();
const products = await getRecommendations();

return {
props: {
data: {
products,
categories,
},
},
};
}) satisfies GetServerSideProps<PageProps>;

export default Home;
Loading
Loading