Skip to content

Commit

Permalink
Refactor content selectors (#55)
Browse files Browse the repository at this point in the history
* Improve selector naming conventions

* Refactor tag selectors to get posts internally

* Fix invalid state error

* Uniform notFound handling everywhere

* Clean up

* Refactor project selectors
  • Loading branch information
melanieseltzer committed Aug 16, 2023
1 parent 41877c2 commit 88f07d1
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 83 deletions.
4 changes: 2 additions & 2 deletions src/app/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Metadata } from 'next';

import { HeroSection } from '~/components/home/HeroSection';

import { getLatestPosts } from '~/entities/blog-post';
import { getLatestBlogPosts } from '~/entities/blog-post';

import { siteConfig } from '~/config/site';

Expand All @@ -17,7 +17,7 @@ export const metadata: Metadata = {
};

export default function Page() {
const posts = getLatestPosts({ limit: MAX_POSTS_DISPLAY });
const posts = getLatestBlogPosts({ limit: MAX_POSTS_DISPLAY });

return (
<>
Expand Down
4 changes: 2 additions & 2 deletions src/app/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Prose } from '~/components/Prose';
import SocialLinks from '~/components/SocialLinks';
import { TechStack } from '~/components/TechStack';

import { getPageContent } from '~/entities/page';
import { getAboutPageContent } from '~/entities/page';

import { siteConfig } from '~/config/site';

Expand All @@ -32,7 +32,7 @@ export async function generateMetadata(
}

export default function AboutPage() {
const content = getPageContent('about');
const content = getAboutPageContent();

return (
<>
Expand Down
18 changes: 7 additions & 11 deletions src/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Metadata, ResolvingMetadata } from 'next';
import { notFound } from 'next/navigation';

import { getBlogPost, getBlogPosts } from '~/entities/blog-post';
import { getAllBlogPosts, getBlogPostBySlug } from '~/entities/blog-post';

import { siteConfig } from '~/config/site';

Expand All @@ -14,12 +14,10 @@ interface Props {
export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata
): Promise<Metadata | undefined> {
const post = getBlogPost(params.slug);
): Promise<Metadata> {
const post = getBlogPostBySlug(params.slug);

if (!post) {
return;
}
if (!post) notFound();

const { title, summary, date, lastModified, tags, slug } = post;

Expand Down Expand Up @@ -49,16 +47,14 @@ export async function generateMetadata(
export const dynamicParams = false;

export function generateStaticParams() {
const posts = getBlogPosts();
const posts = getAllBlogPosts();
return posts.map(({ slug }) => ({ slug }));
}

export default function Page({ params }: Props) {
const post = getBlogPost(params.slug);
const post = getBlogPostBySlug(params.slug);

if (!post) {
notFound();
}
if (!post) notFound();

return <BlogPostPage post={post} />;
}
6 changes: 3 additions & 3 deletions src/app/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PageIntro } from '~/components/PageIntro';
import { Section } from '~/components/Section';
import { Spacer } from '~/components/Spacer';

import { getAllBlogTags, getLatestPosts } from '~/entities/blog-post';
import { getAllBlogPostTags, getLatestBlogPosts } from '~/entities/blog-post';

import { siteConfig } from '~/config/site';

Expand All @@ -31,8 +31,8 @@ export async function generateMetadata(
}

export default function BlogIndexPage() {
const posts = getLatestPosts();
const tags = getAllBlogTags(posts);
const posts = getLatestBlogPosts();
const tags = getAllBlogPostTags();

return (
<>
Expand Down
38 changes: 16 additions & 22 deletions src/app/blog/tags/[tag]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import { notFound } from 'next/navigation';
import { PageIntro } from '~/components/PageIntro';

import {
type Tag,
getAllBlogTags,
getLatestPosts,
getPostPreviews,
getTag,
getAllBlogPostTags,
getTagBySlug,
getTaggedPosts,
} from '~/entities/blog-post';

Expand All @@ -24,11 +21,13 @@ export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const posts = getLatestPosts();
const tagInfo = getTag(posts, params.tag) as Tag;
const tag = getTagBySlug(params.tag);

if (!tag) notFound();

const parentOpenGraph = (await parent).openGraph || {};

const metaTitle = `Posts about ${tagInfo.displayName}`;
const metaTitle = `Posts about ${tag.displayName}`;

return {
title: `${metaTitle} | ${siteConfig.defaultMetaTitle}`,
Expand All @@ -42,33 +41,28 @@ export async function generateMetadata(
export const dynamicParams = false;

export function generateStaticParams() {
const posts = getPostPreviews();
const allTags = getAllBlogTags(posts);

return allTags.map(({ slug }) => ({ tag: slug }));
const tags = getAllBlogPostTags();
return tags.map(({ slug }) => ({ tag: slug }));
}

export default function BlogTagPage({ params: { tag } }: Props) {
const posts = getLatestPosts();
const tagInfo = getTag(posts, tag);
export default function BlogTagPage({ params }: Props) {
const tag = getTagBySlug(params.tag);

if (!tagInfo) {
notFound();
}
if (!tag) notFound();

const allPostsTagged = getTaggedPosts(posts, tag);
const taggedPosts = getTaggedPosts(params.tag);

const count = allPostsTagged.length;
const count = taggedPosts.length;

return (
<>
<PageIntro
reverse
heading={tagInfo.displayName}
heading={tag.displayName}
subheading={`${count} ${count === 1 ? 'post' : 'posts'} tagged:`}
/>

<PostList posts={allPostsTagged} />
<PostList posts={taggedPosts} />
</>
);
}
16 changes: 6 additions & 10 deletions src/app/projects/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Metadata, ResolvingMetadata } from 'next';
import { notFound } from 'next/navigation';

import { getAllProjects, getProject } from '~/entities/project';
import { getAllProjects, getProjectBySlug } from '~/entities/project';

import { ProjectPage } from '../components/ProjectPage';

Expand All @@ -12,12 +12,10 @@ interface Props {
export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata
): Promise<Metadata | undefined> {
const project = getProject(params.slug);
): Promise<Metadata> {
const project = getProjectBySlug(params.slug);

if (!project) {
return;
}
if (!project) notFound();

const { title, summary } = project;
const parentOpenGraph = (await parent).openGraph || {};
Expand All @@ -44,11 +42,9 @@ export function generateStaticParams() {
}

export default function Page({ params }: Props) {
const project = getProject(params.slug);
const project = getProjectBySlug(params.slug);

if (!project) {
notFound();
}
if (!project) notFound();

return <ProjectPage project={project} />;
}
8 changes: 3 additions & 5 deletions src/app/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PageIntro } from '~/components/PageIntro';
import { Section } from '~/components/Section';
import { Spacer } from '~/components/Spacer';

import { getAllProjects, getProjectsByCategory } from '~/entities/project';
import { getAllOssProjects, getAllSideProjects } from '~/entities/project';

import { siteConfig } from '~/config/site';

Expand Down Expand Up @@ -33,10 +33,8 @@ export async function generateMetadata(
}

export default function ProjectsIndexPage() {
const projects = getAllProjects();

const ossProjects = getProjectsByCategory(projects, 'oss');
const sideProjects = getProjectsByCategory(projects, 'sideproject');
const ossProjects = getAllOssProjects();
const sideProjects = getAllSideProjects();

return (
<>
Expand Down
4 changes: 2 additions & 2 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { MetadataRoute } from 'next';

import { getBlogPosts } from '~/entities/blog-post';
import { getAllBlogPosts } from '~/entities/blog-post';
import { getAllProjects } from '~/entities/project';

import { siteConfig } from '~/config/site';

export default function sitemap(): MetadataRoute.Sitemap {
const lastModified = new Date().toISOString().split('T')[0];

const posts = getBlogPosts().map(({ slug, lastModified }) => ({
const posts = getAllBlogPosts().map(({ slug, lastModified }) => ({
url: `${siteConfig.siteUrl}/blog/${slug}`,
lastModified,
}));
Expand Down
14 changes: 7 additions & 7 deletions src/entities/blog-post/selectors/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,26 @@ const extractMetadata = (post: CLBlogPost): BlogPostMetadata => {
// Client selectors
// ==============================

export const getBlogPosts = () =>
export const getAllBlogPosts = () =>
// Hacky assertion to get around typing issue with `tags` under the hood :/
// ref: https://github.com/contentlayerdev/contentlayer/issues/398
allBlogPosts as unknown as CLBlogPost[];

export const getBlogPost = (slug: string): BlogPost | undefined => {
const post = getBlogPosts().find(post => post.slug === slug);
export const getBlogPostBySlug = (slug: string): BlogPost | undefined => {
const post = getAllBlogPosts().find(post => post.slug === slug);
if (!post) return;
return serialize(post);
};

export const getPostPreviews = (): BlogPostMetadata[] =>
getBlogPosts().map(extractMetadata);
export const getBlogPostMetadata = (): BlogPostMetadata[] =>
getAllBlogPosts().map(extractMetadata);

export const getLatestPosts = (
export const getLatestBlogPosts = (
options: { limit?: number } = {}
): BlogPostMetadata[] => {
const { limit } = options;

const posts = getBlogPosts();
const posts = getAllBlogPosts();
const latestPosts = sortPostsByNew(posts);

if (limit) {
Expand Down
22 changes: 11 additions & 11 deletions src/entities/blog-post/selectors/tags.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { BlogPostMetadata, Tag } from '../types';
import type { Tag } from '../types';

export const getAllBlogTags = (posts: BlogPostMetadata[]) => {
import { getBlogPostMetadata } from './posts';

export const getAllBlogPostTags = () => {
const posts = getBlogPostMetadata();
const uniqueTags: Record<string, Tag> = {};

for (const { tags } of posts) {
Expand All @@ -14,13 +17,10 @@ export const getAllBlogTags = (posts: BlogPostMetadata[]) => {
return Object.values(uniqueTags);
};

export const getTag = (posts: BlogPostMetadata[], slug: string) => {
const tags = getAllBlogTags(posts);
return tags.find(tag => tag.slug === slug);
};
export const getTagBySlug = (slug: string) =>
getAllBlogPostTags().find(tag => tag.slug === slug);

export const getTaggedPosts = (
posts: BlogPostMetadata[],
tag: string
): BlogPostMetadata[] =>
posts.filter(post => post.tags.map(tag => tag.slug).includes(tag));
export const getTaggedPosts = (tag: string) =>
getBlogPostMetadata().filter(post =>
post.tags.map(tag => tag.slug).includes(tag)
);
7 changes: 4 additions & 3 deletions src/entities/page/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { allPages } from 'contentlayer/generated';
import type { ConfiguredMDXPages, Page } from './types';

export const getPageContent = (slug: ConfiguredMDXPages) =>
// This will only ever be called with known slugs (ConfiguredMDXPages)
// aka pages we actually have content for, so it's safe to assert that
// we'll always find a page in this case (it can't potentially be undefined).
// Will never be called with a slug that doesn't exist (isn't a configured page),
// so we can safely assert a Page will always be found here.
allPages.find(page => page.slug === slug) as Page;

export const getAboutPageContent = () => getPageContent('about');
12 changes: 7 additions & 5 deletions src/entities/project/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ const serialize = (project: CLProject): Project => ({

export const getAllProjects = (): Project[] => allProjects.map(serialize);

export const getProject = (slug: string) =>
export const getProjectBySlug = (slug: string) =>
getAllProjects().find(project => project.slug === slug);

export const getProjectsByCategory = (
projects: Project[],
category: ProjectCategory
): Project[] => projects.filter(project => project.category === category);
export const getProjectsByCategory = (category: ProjectCategory): Project[] =>
getAllProjects().filter(project => project.category === category);

export const getAllOssProjects = () => getProjectsByCategory('oss');

export const getAllSideProjects = () => getProjectsByCategory('sideproject');

1 comment on commit 88f07d1

@vercel
Copy link

@vercel vercel bot commented on 88f07d1 Aug 16, 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.