Skip to content

Commit

Permalink
Reduce unnecessary requests to drupal for 404 pages
Browse files Browse the repository at this point in the history
  • Loading branch information
pookmish committed Jan 5, 2024
1 parent 1222580 commit 7dcd46f
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 61 deletions.
52 changes: 13 additions & 39 deletions app/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {notFound, redirect} from "next/navigation";
import NodePage from "@components/nodes/pages/node-page";
import {GetStaticPathsResult, Metadata} from "next";
import {DrupalJsonApiParams} from "drupal-jsonapi-params";
import {getPathsFromContext} from "@lib/drupal/get-paths";
import {Metadata} from "next";
import {getAllDrupalPaths, pathIsValid} from "@lib/drupal/get-paths";
import {getNodeMetadata} from "./metadata";
import {getPathFromContext, isDraftMode} from "@lib/drupal/utils";
import {PageProps, Params, StanfordNode} from "@lib/types";
Expand All @@ -15,6 +14,9 @@ import RedirectError from "@lib/redirect-error";
export const revalidate = false;

const Page = async ({params}: PageProps) => {
const path = getPathFromContext({params})
if (!await pathIsValid(path)) notFound();

let node = null;
try {
node = await getPageData(params);
Expand All @@ -31,6 +33,7 @@ const Page = async ({params}: PageProps) => {

export const generateMetadata = async ({params}: PageProps): Promise<Metadata> => {
const path = getPathFromContext({params})
if (!await pathIsValid(path, 'node')) return {};

try {
const node = await getResourceByPath<StanfordNode>(path)
Expand Down Expand Up @@ -62,43 +65,14 @@ const getPageData = async(params: Params): Promise<StanfordNode | undefined> =>
}

export const generateStaticParams = async () => {
const params = new DrupalJsonApiParams();
// Add a simple include so that it doesn't fetch all the data right now. The full node data comes later, we only need
// the node paths.
params.addInclude(['node_type']);
params.addPageLimit(50);

const contentTypes = [
'node--stanford_page',
'node--stanford_event',
'node--stanford_news',
'node--stanford_person',
'node--stanford_policy',
'node--stanford_publication',
'node--stanford_course',
]

// Use JSON API to fetch the list of all node paths on the site.
let paths: GetStaticPathsResult["paths"] = await getPathsFromContext(contentTypes, {params: params.getQueryObject()});

const completeBuild = process.env.BUILD_COMPLETE === 'true';

let fetchMore = completeBuild;
let fetchedData: GetStaticPathsResult["paths"] = []
let page = 1;
while (fetchMore) {
console.log('Fetching page ' + page);
params.addPageOffset(page * 50);

fetchedData = await getPathsFromContext(contentTypes, {params: params.getQueryObject()})
paths = [...paths, ...fetchedData];
fetchMore = fetchedData.length > 0;
page++;
}
const allPaths = await getAllDrupalPaths();
const nodePaths = allPaths.get('node');

return paths.filter(path => typeof path !== 'object' || path.params?.slug?.[0])
.map(path => typeof path === "object" ? path?.params : path)
.slice(0, (completeBuild ? -1 : 1))
let params: Params[] = [];
if (nodePaths) {
params = nodePaths.map(path => ({slug: path.split('/')}))
}
return process.env.BUILD_COMPLETE === 'true' ? params : params.slice(0, 1);
}

export default Page;
3 changes: 2 additions & 1 deletion app/api/app-revalidate/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const GET = async (request: NextRequest) => {
if (!slug) {
return NextResponse.json({message: 'Missing slug'}, {status: 400});
}

revalidatePath(slug)
return NextResponse.json({revalidated: true});
return NextResponse.json({revalidated: true, path: slug});
}
31 changes: 16 additions & 15 deletions app/api/draft/route.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
// route handler with secret and slug
import {draftMode} from 'next/headers'
import {redirect} from 'next/navigation'
import {NextRequest, NextResponse} from "next/server";
import {getResourceByPath} from "@lib/drupal/get-resource";
import {StanfordNode} from "@lib/types";

export async function GET(request: Request) {
// Parse query string parameters
const {searchParams} = new URL(request.url)
const secret = searchParams.get('secret')
const slug = searchParams.get('slug')
export async function GET(request: NextRequest) {
const secret = request.nextUrl.searchParams.get('secret')
const slug = request.nextUrl.searchParams.get('slug')

// Check the secret and next parameters
// This secret should only be known to this route handler and the CMS
if (secret !== process.env.DRUPAL_PREVIEW_SECRET || !slug) {
return new Response('Invalid token', {status: 401})
if (secret !== process.env.DRUPAL_PREVIEW_SECRET) {
return NextResponse.json({message: 'Invalid token'}, {status: 401})
}

if (!slug) {
return NextResponse.json({message: 'Invalid slug path'}, {status: 401})
}
// Enable Draft Mode by setting the cookie
draftMode().enable()

// Fetch the headless CMS to check if the provided `slug` exists
// getPostBySlug would implement the required fetching logic to the headless CMS
const node = await getResourceByPath<StanfordNode>(slug);
const node = await getResourceByPath<StanfordNode>(slug, {draftMode: true})

// If the slug doesn't exist prevent draft mode from being enabled
// If the slug doesn't exist prevent disable draft mode and return
if (!node) {
return new Response('Invalid slug', {status: 401})
return NextResponse.json({message: 'Invalid slug'}, {status: 401})
}

draftMode().enable()

// Redirect to the path from the fetched post
// We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities
redirect(node.path.alias)
redirect(slug)
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const StanfordPersonPage = ({node, ...props}: Props) => {
</div>

<section className="flex flex-col lg:flex-row">
<div>
<div className="flex-1 shrink-0">
{node.body && <Wysiwyg html={node.body}/>}

{node.su_person_components &&
Expand Down
84 changes: 79 additions & 5 deletions src/lib/drupal/get-paths.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,89 @@
import {GetStaticPathsResult} from "next";
import {AccessToken, JsonApiParams, JsonApiResourceWithPath} from "next-drupal";
import {getResourceCollection} from "@lib/drupal/get-resource";
import {Params} from "@lib/types";
import {DrupalRedirect, PageProps, Params} from "@lib/types";
import {DrupalJsonApiParams} from "drupal-jsonapi-params";
import {getPathFromContext, isDraftMode} from "@lib/drupal/utils";

export const pathIsValid = async (path: string, type?: 'node' | 'redirect') => {
if (isDraftMode()) return true;
const drupalPaths = await getAllDrupalPaths();
if (type) {
return drupalPaths.get(type)?.includes(path);
}
let allPaths: string[] = [];
drupalPaths.forEach(typePaths => allPaths = [...allPaths, ...typePaths])
return allPaths.includes(path);
}

export const getAllDrupalPaths = async (): Promise<Map<string, string[]>> => {
const paths = new Map();
paths.set('node', await getNodePaths())
paths.set('redirect', await getRedirectPaths())
return paths;
}

const getNodePaths = async (): Promise<string[]> => {
const params = new DrupalJsonApiParams();
// Add a simple include so that it doesn't fetch all the data right now. The full node data comes later, we only need
// the node paths.
params.addInclude(['node_type']);
params.addPageLimit(50);

const contentTypes = [
'node--stanford_page',
'node--stanford_event',
'node--stanford_news',
'node--stanford_person',
'node--stanford_policy',
'node--stanford_publication',
'node--stanford_course',
]

let paths: PageProps[] = [];

let fetchMore = true;
let fetchedData: PageProps[] = []
let page = 0;
while (fetchMore) {
params.addPageOffset(page * 50);

// Use JSON API to fetch the list of all node paths on the site.
fetchedData = await getPathsFromContext(contentTypes, {params: params.getQueryObject()})
paths = [...paths, ...fetchedData];
fetchMore = fetchedData.length > 0;
page++;
}
return paths.map(pagePath => getPathFromContext(pagePath)).filter(path => !!path);
}

const getRedirectPaths = async (): Promise<string[]> => {
const params = new DrupalJsonApiParams();
params.addPageLimit(50);

let redirects: DrupalRedirect[] = []
let fetchMore = true;
let fetchedData: DrupalRedirect[] = []
let page = 0;

while (fetchMore) {
params.addPageOffset(page * 50);

// Use JSON API to fetch the list of all node paths on the site.
fetchedData = await getResourceCollection<DrupalRedirect>('redirect--redirect', {params: params.getQueryObject()})
redirects = [...redirects, ...fetchedData];

fetchMore = fetchedData.length === 50;
page++;
}
return redirects.map(redirect => redirect.redirect_source.path)
}

export const getPathsFromContext = async (
types: string | string[],
options: { params?: JsonApiParams,accessToken?: AccessToken } = {}
): Promise<GetStaticPathsResult["paths"]> => {
options: { params?: JsonApiParams, accessToken?: AccessToken } = {}
): Promise<PageProps[]> => {
if (typeof types === "string") types = [types]


const paths = await Promise.all<{ params: Params }[]>(
types.map(async (type) => {
const typeOptions = {...options};
Expand Down
15 changes: 15 additions & 0 deletions src/lib/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -563,3 +563,18 @@ export type PublicationCitationType = JsonApiResource & {
su_url?: DrupalLinkFieldType
su_year?: number
}

export type DrupalRedirect = JsonApiResource & {
redirect_source: {
path: string,
query: []
},
redirect_redirect: {
uri: string,
title: string,
options: [],
target_uuid: string,
url: string
},
status_code: number
}
1 change: 1 addition & 0 deletions src/pages/api/revalidate.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {NextApiRequest, NextApiResponse} from "next";


// The app router revalidation doesn't work on Vercel, but the pages router does.
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// Check for secret to confirm this is a valid request.
Expand Down

1 comment on commit 7dcd46f

@vercel
Copy link

@vercel vercel bot commented on 7dcd46f Jan 5, 2024

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.