Skip to content

Commit

Permalink
meta: refactor to an inteligent cache system
Browse files Browse the repository at this point in the history
  • Loading branch information
ovflowd committed Nov 22, 2023
1 parent fe0c20f commit 4dc0aba
Show file tree
Hide file tree
Showing 25 changed files with 302 additions and 151 deletions.
25 changes: 14 additions & 11 deletions app/[locale]/feed/[feed]/route.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
import { NextResponse } from 'next/server';

import { websiteFeeds } from '@/next.data.mjs';
import provideWebsiteFeeds from '@/next-data/providers/websiteFeeds';
import { siteConfig } from '@/next.json.mjs';
import { defaultLocale } from '@/next.locales.mjs';

type StaticParams = { params: { feed: string } };
// We only support fetching these pages from the /en/ locale code
const locale = defaultLocale.code;

type StaticParams = { params: { feed: string; locale: string } };

// This is the Route Handler for the `GET` method which handles the request
// for Blog Feeds within the Node.js Website
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
export const GET = (_: Request, { params }: StaticParams) => {
if (params.feed.includes('.xml') && websiteFeeds.has(params.feed)) {
return new NextResponse(websiteFeeds.get(params.feed)?.rss2(), {
headers: { 'Content-Type': 'application/xml' },
});
}
export const GET = async (_: Request, { params }: StaticParams) => {
const websiteFeed = await provideWebsiteFeeds(params.feed);

return new NextResponse(null, { status: 404 });
return new NextResponse(websiteFeed, {
headers: { 'Content-Type': 'application/xml' },
status: websiteFeed ? 200 : 404,
});
};

// This function generates the static paths that come from the dynamic segments
// `en/feeds/[feed]` and returns an array of all available static paths
// this is useful for static exports, for example.
// Note that differently from the App Router these don't get built at the build time
// only if the export is already set for static export
export const generateStaticParams = () =>
[...websiteFeeds.keys()].map(feed => ({ feed, locale: defaultLocale.code }));
export const generateStaticParams = async () =>
siteConfig.rssFeeds.map(feed => ({ feed: feed.file, locale }));

// Enforces that this route is used as static rendering
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
Expand Down
39 changes: 39 additions & 0 deletions app/[locale]/next-data/blog-data/[category]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import provideBlogData from '@/next-data/providers/blogData';
import { defaultLocale } from '@/next.locales.mjs';

// We only support fetching these pages from the /en/ locale code
const locale = defaultLocale.code;

type StaticParams = { params: { category: string; locale: string } };

// This is the Route Handler for the `GET` method which handles the request
// for generating our static data for the Node.js Website
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
export const GET = async (_: Request, { params }: StaticParams) => {
const { posts, pagination } = await provideBlogData(params.category);

return Response.json(
{ posts, pagination },
{ status: posts.length ? 200 : 404 }
);
};

// This function generates the static paths that come from the dynamic segments
// `en/next-data/[type]` and returns an array of all available static paths
// this is useful for static exports, for example.
// Note that differently from the App Router these don't get built at the build time
// only if the export is already set for static export
export const generateStaticParams = async () => {
const {
meta: { categories, pagination },
} = await provideBlogData();

return [
...categories.map(category => ({ category, locale })),
...pagination.map(year => ({ category: `year-${year}`, locale })),
];
};

// Enforces that this route is used as static rendering
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
export const dynamic = 'error';
25 changes: 25 additions & 0 deletions app/[locale]/next-data/release-data/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import provideReleaseData from '@/next-data/providers/releaseData';
import { defaultLocale } from '@/next.locales.mjs';

// We only support fetching these pages from the /en/ locale code
const locale = defaultLocale.code;

// This is the Route Handler for the `GET` method which handles the request
// for generating our static data for the Node.js Website
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
export const GET = async () => {
const releaseData = await provideReleaseData();

return Response.json(releaseData);
};

// This function generates the static paths that come from the dynamic segments
// `en/next-data/[type]` and returns an array of all available static paths
// this is useful for static exports, for example.
// Note that differently from the App Router these don't get built at the build time
// only if the export is already set for static export
export const generateStaticParams = async () => [{ locale }];

// Enforces that this route is used as static rendering
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
export const dynamic = 'error';
6 changes: 4 additions & 2 deletions components/Docs/NodeApiVersionLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import getReleaseData from '@/next-data/releaseData';
import { DOCS_URL } from '@/next.constants.mjs';
import { releaseData } from '@/next.data.mjs';

const NodeApiVersionLinks = () => {
const NodeApiVersionLinks = async () => {
const releaseData = await getReleaseData();

// Gets all major releases without the 0x release as those are divided on 0.12x and 0.10x
const mappedReleases = releaseData.slice(0, -1).map(({ major }) => (
<li key={major}>
Expand Down
10 changes: 6 additions & 4 deletions components/Downloads/DownloadReleasesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import type { FC } from 'react';

import { releaseData } from '@/next.data.mjs';
import getReleaseData from '@/next-data/releaseData';
import { getNodeApiLink } from '@/util/getNodeApiLink';
import { getNodejsChangelog } from '@/util/getNodeJsChangelog';

const DownloadReleasesTable: FC = () => {
const t = useTranslations();
const DownloadReleasesTable: FC = async () => {
const releaseData = await getReleaseData();

const t = await getTranslations();

return (
<table id="tbVersions" className="download-table full-width">
Expand Down
2 changes: 1 addition & 1 deletion components/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { FC } from 'react';

import Link from '@/components/Link';

type PaginationProps = { prev?: number; next?: number };
type PaginationProps = { prev?: number | null; next?: number | null };

const Pagination: FC<PaginationProps> = ({ next, prev }) => {
const t = useTranslations();
Expand Down
6 changes: 4 additions & 2 deletions components/withNodeRelease.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import type { FC } from 'react';

import { releaseData } from '@/next.data.mjs';
import getReleaseData from '@/next-data/releaseData';
import type { NodeRelease, NodeReleaseStatus } from '@/types';

type WithNodeReleaseProps = {
status: NodeReleaseStatus[] | NodeReleaseStatus;
children: FC<{ release: NodeRelease }>;
};

export const WithNodeRelease: FC<WithNodeReleaseProps> = ({
export const WithNodeRelease: FC<WithNodeReleaseProps> = async ({
status,
children: Component,
}) => {
const releaseData = await getReleaseData();

const matchingRelease = releaseData.find(release =>
[status].flat().includes(release.status)
);
Expand Down
1 change: 0 additions & 1 deletion hooks/react-client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ export { default as useDetectOS } from './useDetectOS';
export { default as useMediaQuery } from './useMediaQuery';
export { default as useNotification } from './useNotification';
export { default as useClientContext } from './useClientContext';
export { default as useBlogData } from './useBlogData';
export { default as useSiteNavigation } from './useSiteNavigation';
7 changes: 0 additions & 7 deletions hooks/react-client/useBlogData.ts

This file was deleted.

1 change: 0 additions & 1 deletion hooks/react-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ export { default as useDetectOS } from './useDetectOS';
export { default as useMediaQuery } from './useMediaQuery';
export { default as useNotification } from './useNotification';
export { default as useClientContext } from './useClientContext';
export { default as useBlogData } from './useBlogData';
export { default as useSiteNavigation } from './useSiteNavigation';
57 changes: 0 additions & 57 deletions hooks/react-server/useBlogData.ts

This file was deleted.

65 changes: 31 additions & 34 deletions layouts/BlogCategoryLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,50 @@
import { useTranslations } from 'next-intl';
import { useMemo } from 'react';
import { getTranslations } from 'next-intl/server';
import type { FC } from 'react';

import { getClientContext } from '@/client-context';
import { Time } from '@/components/Common/Time';
import Link from '@/components/Link';
import Pagination from '@/components/Pagination';
import { useClientContext, useBlogData } from '@/hooks/server';
import type { BlogPost } from '@/types';
import getBlogData from '@/next-data/blogData';

const BlogCategoryLayout: FC = () => {
const t = useTranslations();
const { getPagination, getPostsByYear, getPostsByCategory, currentCategory } =
useBlogData();
const getCurrentCategory = (pathname: string) => {
// We split the pathname to retrieve the blog category from it since the
// URL is usually blog/{category} the second path piece is usually the
// category name
const [_pathname, category] = pathname.split('/');

const { frontmatter } = useClientContext();
if (_pathname === 'blog' && category && category.length) {
return category;
}

const { posts, pagination, title } = useMemo(() => {
if (currentCategory.startsWith('year-')) {
const categoryWithoutPrefix = currentCategory.replace('year-', '');
// if either the pathname does not match to a blog page
// which should not happen (as this hook should only be used in blog pages)
// or if there is no category in the URL we return the current year as category name
// which is always the default category (for example, the blog index)
return `year-${new Date().getFullYear()}`;
};

const BlogCategoryLayout: FC = async () => {
const { frontmatter, pathname } = getClientContext();
const category = getCurrentCategory(pathname);

const t = await getTranslations();

const { posts, pagination } = await getBlogData(category);

return {
posts: getPostsByYear(categoryWithoutPrefix),
pagination: getPagination(categoryWithoutPrefix),
title: t('layouts.blogIndex.currentYear', {
year: categoryWithoutPrefix,
}),
};
}
// this only applies if current category is a year category
const year = category.replace('year-', '');

return {
posts: getPostsByCategory(currentCategory),
pagination: undefined,
title: frontmatter.title,
};
}, [
currentCategory,
frontmatter.title,
getPagination,
getPostsByCategory,
getPostsByYear,
t,
]);
const title = category.startsWith('year-')
? t('layouts.blogIndex.currentYear', { year })
: frontmatter.title;

return (
<div className="container" dir="auto">
<h2>{title}</h2>

<ul className="blog-index">
{posts.map((post: BlogPost) => (
{posts.map(post => (
<li key={post.slug}>
<Time
date={post.date}
Expand Down
6 changes: 4 additions & 2 deletions layouts/DocsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import type { RichTranslationValues } from 'next-intl';
import type { FC, PropsWithChildren } from 'react';

import SideNavigation from '@/components/SideNavigation';
import { releaseData } from '@/next.data.mjs';
import getReleaseData from '@/next-data/releaseData';

const DocsLayout: FC<PropsWithChildren> = async ({ children }) => {
const releaseData = await getReleaseData();

const DocsLayout: FC<PropsWithChildren> = ({ children }) => {
const [lts, current] = [
releaseData.find(({ isLts }) => isLts),
releaseData.find(({ status }) => status === 'Current'),
Expand Down
19 changes: 19 additions & 0 deletions next-data/blogData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ENABLE_STATIC_EXPORT, NEXT_DATA_URL } from '@/next.constants.mjs';
import type { BlogDataRSC } from '@/types';

const getBlogData = (category: string): Promise<BlogDataRSC> => {
if (ENABLE_STATIC_EXPORT) {
// Loads the data dynamically with lazy-loading to prevent data-generation
// within the top level import
return import('@/next-data/providers/blogData').then(
({ default: provideBlogData }) => provideBlogData(category)
);
}

// When we're on RSC with Server capabilities we prefer using Next.js Data Fetching
// as this will load cached data from the server instead of generating data on the fly
// this is extremely useful for ISR and SSG as it will not generate this data on every request
return fetch(`${NEXT_DATA_URL}blog-data/${category}`).then(r => r.json());
};

export default getBlogData;
Loading

0 comments on commit 4dc0aba

Please sign in to comment.