From 9be41b5e5cfafc731d01c3c933a380fac57f0a56 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Sun, 10 Jul 2022 17:26:22 -0400 Subject: [PATCH] separate page/site stats API endpoints to lower memory usage --- .node-version | 2 +- components/HitCounter/HitCounter.tsx | 2 +- package.json | 2 +- pages/api/count.ts | 47 ++++++++++++++++++++++++++++ pages/api/hits.ts | 45 +++++--------------------- pages/projects.tsx | 4 +-- 6 files changed, 58 insertions(+), 44 deletions(-) create mode 100644 pages/api/count.ts diff --git a/.node-version b/.node-version index d9289897..431076a9 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -16.15.1 +16.16.0 diff --git a/components/HitCounter/HitCounter.tsx b/components/HitCounter/HitCounter.tsx index 10350115..d05e5bcd 100644 --- a/components/HitCounter/HitCounter.tsx +++ b/components/HitCounter/HitCounter.tsx @@ -12,7 +12,7 @@ const HitCounter = ({ slug }: HitCounterProps) => { // use immutable SWR to avoid double (or more) counting views: // https://swr.vercel.app/docs/revalidation#disable-automatic-revalidations const { data, error } = useSWRImmutable( - `/api/hits/?${new URLSearchParams({ + `/api/count/?${new URLSearchParams({ slug, })}`, fetcher diff --git a/package.json b/package.json index 64501514..2fa47915 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ }, "packageManager": "yarn@1.22.19", "volta": { - "node": "16.15.1", + "node": "16.16.0", "yarn": "1.22.19" } } diff --git a/pages/api/count.ts b/pages/api/count.ts new file mode 100644 index 00000000..76d68ede --- /dev/null +++ b/pages/api/count.ts @@ -0,0 +1,47 @@ +import { prisma } from "../../lib/helpers/prisma"; +import { logServerError } from "../../lib/helpers/sentry"; +import type { NextApiRequest, NextApiResponse } from "next"; +import type { PageStats } from "../../types"; + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + try { + if (!req.query?.slug) { + return res.status(400).json({ message: "Missing `slug` parameter." }); + } + + // add one to this page's count and return the new number + const result = await incrementPageHits(req.query.slug as string); + + // disable caching on both ends + res.setHeader("Cache-Control", "private, no-cache, no-store, must-revalidate"); + + // send result as JSON + return res.status(200).json(result); + } catch (error) { + // extract just the error message to send back to client + const message = error instanceof Error ? error.message : error; + + // log full error to console and sentry + await logServerError(error); + + // 500 Internal Server Error + return res.status(500).json({ message }); + } +}; + +const incrementPageHits = async (slug: string): Promise => { + const { hits } = await prisma.hits.upsert({ + where: { slug }, + create: { slug }, + update: { + hits: { + increment: 1, + }, + }, + }); + + // send client the *new* hit count + return { hits }; +}; + +export default handler; diff --git a/pages/api/hits.ts b/pages/api/hits.ts index bf31d8eb..07d29db7 100644 --- a/pages/api/hits.ts +++ b/pages/api/hits.ts @@ -3,37 +3,21 @@ import { getAllNotes } from "../../lib/helpers/parse-notes"; import { logServerError } from "../../lib/helpers/sentry"; import { NOTES_DIR } from "../../lib/config/constants"; import type { NextApiRequest, NextApiResponse } from "next"; -import type { PageStats, DetailedPageStats, SiteStats } from "../../types"; +import type { DetailedPageStats, SiteStats } from "../../types"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { try { - if (req.method !== "GET") { - // 405 Method Not Allowed - return res.status(405).end(); - } + // return overall site stats if slug not specified + const result = await getSiteStats(); - const { slug } = req.query; - let data; - - if (slug) { - // add one to this page's count and return the new number - data = await incrementPageHits(slug as string); - - // disable caching on both ends - res.setHeader("Cache-Control", "private, no-cache, no-store, must-revalidate"); - } else { - // return overall site stats if slug not specified - data = await getSiteStats(); - - // let Vercel edge cache results for 15 mins - res.setHeader("Cache-Control", "public, max-age=0, s-maxage=900, stale-while-revalidate"); - } + // let Vercel edge cache results for 15 mins + res.setHeader("Cache-Control", "public, max-age=0, s-maxage=900, stale-while-revalidate"); // send result as JSON - return res.status(200).json(data); + return res.status(200).json(result); } catch (error) { // extract just the error message to send back to client - const message = error instanceof Error ? error.message : "Unknown error."; + const message = error instanceof Error ? error.message : error; // log full error to console and sentry await logServerError(error); @@ -43,21 +27,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } }; -const incrementPageHits = async (slug: string): Promise => { - const { hits } = await prisma.hits.upsert({ - where: { slug }, - create: { slug }, - update: { - hits: { - increment: 1, - }, - }, - }); - - // send client the *new* hit count - return { hits }; -}; - const getSiteStats = async (): Promise => { // simultaneously fetch the entire hits db and notes from the filesystem const [hits, notes] = await Promise.all([ diff --git a/pages/projects.tsx b/pages/projects.tsx index c2fc73d8..67897b83 100644 --- a/pages/projects.tsx +++ b/pages/projects.tsx @@ -79,9 +79,7 @@ export const getStaticProps: GetStaticProps<{ console.warn(`ERROR: I can't fetch any GitHub projects without "GH_PUBLIC_TOKEN" set! Skipping for now...`); return { - props: { - repos: [], - }, + notFound: true, }; }