Skip to content

Commit

Permalink
Feature/app router (#213)
Browse files Browse the repository at this point in the history
* wip

* fonts

* page animations

* mv

* wip

* middleware

* mv

* wip

* fix link

* slug server page

* fix

* ts

* single

* slug from params

* e2e test updates w/o serversideprops

* warnings

* cleanup

* e2e node version

* wait

* shared var

* remove old config val
  • Loading branch information
nlkluth authored Dec 6, 2023
1 parent 82e89ea commit d97b7a6
Show file tree
Hide file tree
Showing 52 changed files with 600 additions and 546 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16.x
node-version: 18.x

- uses: pnpm/[email protected]
with:
Expand Down
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
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>
);
}
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

1 comment on commit d97b7a6

@vercel
Copy link

@vercel vercel bot commented on d97b7a6 Dec 6, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.