upstream #1
@ -2,6 +2,9 @@ import { Authors, allAuthors } from 'contentlayer/generated'
|
|||||||
import { Mdx } from '@/components/MDXComponents'
|
import { Mdx } from '@/components/MDXComponents'
|
||||||
import AuthorLayout from '@/layouts/AuthorLayout'
|
import AuthorLayout from '@/layouts/AuthorLayout'
|
||||||
import { coreContent } from 'pliny/utils/contentlayer'
|
import { coreContent } from 'pliny/utils/contentlayer'
|
||||||
|
import { genPageMetadata } from 'app/seo'
|
||||||
|
|
||||||
|
export const metadata = genPageMetadata({ title: 'About' })
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const author = allAuthors.find((p) => p.slug === 'default') as Authors
|
const author = allAuthors.find((p) => p.slug === 'default') as Authors
|
||||||
|
@ -4,6 +4,61 @@ import { sortedBlogPost, coreContent } from 'pliny/utils/contentlayer'
|
|||||||
import { allBlogs, allAuthors } from 'contentlayer/generated'
|
import { allBlogs, allAuthors } from 'contentlayer/generated'
|
||||||
import type { Authors, Blog } from 'contentlayer/generated'
|
import type { Authors, Blog } from 'contentlayer/generated'
|
||||||
import PostLayout from '@/layouts/PostLayout'
|
import PostLayout from '@/layouts/PostLayout'
|
||||||
|
import { Metadata } from 'next'
|
||||||
|
import siteMetadata from '@/data/siteMetadata'
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: { slug: string[] }
|
||||||
|
}): Promise<Metadata | undefined> {
|
||||||
|
const slug = params.slug.join('/')
|
||||||
|
const post = allBlogs.find((p) => p.slug === slug)
|
||||||
|
const authorList = post?.authors || ['default']
|
||||||
|
const authorDetails = authorList.map((author) => {
|
||||||
|
const authorResults = allAuthors.find((p) => p.slug === author)
|
||||||
|
return coreContent(authorResults as Authors)
|
||||||
|
})
|
||||||
|
if (!post) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishedAt = new Date(post.date).toISOString()
|
||||||
|
const modifiedAt = new Date(post.lastmod || post.date).toISOString()
|
||||||
|
const authors = authorDetails.map((author) => author.name)
|
||||||
|
let imageList = [siteMetadata.socialBanner]
|
||||||
|
if (post.images) {
|
||||||
|
imageList = typeof post.images === 'string' ? [post.images] : post.images
|
||||||
|
}
|
||||||
|
const ogImages = imageList.map((img) => {
|
||||||
|
return {
|
||||||
|
url: img.includes('http') ? img : siteMetadata.siteUrl + img,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: post.title,
|
||||||
|
description: post.summary,
|
||||||
|
openGraph: {
|
||||||
|
title: post.title,
|
||||||
|
description: post.summary,
|
||||||
|
siteName: siteMetadata.title,
|
||||||
|
locale: 'en_US',
|
||||||
|
type: 'article',
|
||||||
|
publishedTime: publishedAt,
|
||||||
|
modifiedTime: modifiedAt,
|
||||||
|
url: './',
|
||||||
|
images: ogImages,
|
||||||
|
authors: authors.length > 0 ? authors : [siteMetadata.author],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: 'summary_large_image',
|
||||||
|
title: post.title,
|
||||||
|
description: post.summary,
|
||||||
|
images: imageList,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const generateStaticParams = async () => {
|
export const generateStaticParams = async () => {
|
||||||
const paths = allBlogs.map((p) => ({ slug: p.slug.split('/') }))
|
const paths = allBlogs.map((p) => ({ slug: p.slug.split('/') }))
|
||||||
|
@ -2,9 +2,12 @@ import ListLayout from '@/layouts/ListLayout'
|
|||||||
import { sortedBlogPost } from 'pliny/utils/contentlayer'
|
import { sortedBlogPost } from 'pliny/utils/contentlayer'
|
||||||
import { allBlogs } from 'contentlayer/generated'
|
import { allBlogs } from 'contentlayer/generated'
|
||||||
import type { Blog } from 'contentlayer/generated'
|
import type { Blog } from 'contentlayer/generated'
|
||||||
|
import { genPageMetadata } from 'app/seo'
|
||||||
|
|
||||||
const POSTS_PER_PAGE = 5
|
const POSTS_PER_PAGE = 5
|
||||||
|
|
||||||
|
export const metadata = genPageMetadata({ title: 'Blog' })
|
||||||
|
|
||||||
export default function BlogPage() {
|
export default function BlogPage() {
|
||||||
const posts = sortedBlogPost(allBlogs) as Blog[]
|
const posts = sortedBlogPost(allBlogs) as Blog[]
|
||||||
const pageNumber = 1
|
const pageNumber = 1
|
||||||
|
@ -8,14 +8,61 @@ import SectionContainer from '@/components/SectionContainer'
|
|||||||
import Footer from '@/components/Footer'
|
import Footer from '@/components/Footer'
|
||||||
import siteMetadata from '@/data/siteMetadata'
|
import siteMetadata from '@/data/siteMetadata'
|
||||||
import { ThemeProviders } from './theme-providers'
|
import { ThemeProviders } from './theme-providers'
|
||||||
|
import { Metadata } from 'next'
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
subsets: ['latin'],
|
subsets: ['latin'],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
metadataBase: new URL(siteMetadata.siteUrl),
|
||||||
|
title: {
|
||||||
|
default: siteMetadata.title,
|
||||||
|
template: `%s | ${siteMetadata.title}`,
|
||||||
|
},
|
||||||
|
description: siteMetadata.description,
|
||||||
|
openGraph: {
|
||||||
|
title: siteMetadata.title,
|
||||||
|
description: siteMetadata.description,
|
||||||
|
url: './',
|
||||||
|
siteName: siteMetadata.title,
|
||||||
|
images: [siteMetadata.socialBanner],
|
||||||
|
locale: 'en_US',
|
||||||
|
type: 'website',
|
||||||
|
},
|
||||||
|
alternates: {
|
||||||
|
canonical: './',
|
||||||
|
},
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
googleBot: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
'max-video-preview': -1,
|
||||||
|
'max-image-preview': 'large',
|
||||||
|
'max-snippet': -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
title: siteMetadata.title,
|
||||||
|
card: 'summary_large_image',
|
||||||
|
images: [siteMetadata.socialBanner],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<html lang={siteMetadata.language} className={`${inter.className} scroll-smooth`}>
|
<html lang={siteMetadata.language} className={`${inter.className} scroll-smooth`}>
|
||||||
|
<link rel="apple-touch-icon" sizes="76x76" href="/static/favicons/apple-touch-icon.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.png" />
|
||||||
|
<link rel="manifest" href="/static/favicons/site.webmanifest" />
|
||||||
|
<link rel="mask-icon" href="/static/favicons/safari-pinned-tab.svg" color="#5bbad5" />
|
||||||
|
<meta name="msapplication-TileColor" content="#000000" />
|
||||||
|
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fff" />
|
||||||
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000" />
|
||||||
|
<link rel="alternate" type="application/rss+xml" href="/feed.xml" />
|
||||||
<body className="bg-white text-black antialiased dark:bg-gray-900 dark:text-white">
|
<body className="bg-white text-black antialiased dark:bg-gray-900 dark:text-white">
|
||||||
<ThemeProviders>
|
<ThemeProviders>
|
||||||
{/* <Analytics analyticsConfig={siteMetadata.analytics} /> */}
|
{/* <Analytics analyticsConfig={siteMetadata.analytics} /> */}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import projectsData from '@/data/projectsData'
|
import projectsData from '@/data/projectsData'
|
||||||
import Card from '@/components/Card'
|
import Card from '@/components/Card'
|
||||||
|
import { genPageMetadata } from 'app/seo'
|
||||||
|
|
||||||
|
export const metadata = genPageMetadata({ title: 'Projects' })
|
||||||
|
|
||||||
export default function Projects() {
|
export default function Projects() {
|
||||||
return (
|
return (
|
||||||
|
28
app/seo.tsx
Normal file
28
app/seo.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Metadata } from 'next'
|
||||||
|
import siteMetadata from '@/data/siteMetadata'
|
||||||
|
|
||||||
|
interface PageSEOProps {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
image?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function genPageMetadata({ title, description, image }: PageSEOProps): Metadata {
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
openGraph: {
|
||||||
|
title: `${title} | ${siteMetadata.title}`,
|
||||||
|
description: description || siteMetadata.description,
|
||||||
|
url: './',
|
||||||
|
siteName: siteMetadata.title,
|
||||||
|
images: image ? [image] : [siteMetadata.socialBanner],
|
||||||
|
locale: 'en_US',
|
||||||
|
type: 'website',
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
title: `${title} | ${siteMetadata.title}`,
|
||||||
|
card: 'summary_large_image',
|
||||||
|
images: image ? [image] : [siteMetadata.socialBanner],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,11 @@ import { getAllTags } from 'pliny/utils/contentlayer'
|
|||||||
import Link from '@/components/Link'
|
import Link from '@/components/Link'
|
||||||
// import { PageSEO } from '@/components/SEO'
|
// import { PageSEO } from '@/components/SEO'
|
||||||
import Tag from '@/components/Tag'
|
import Tag from '@/components/Tag'
|
||||||
import siteMetadata from '@/data/siteMetadata'
|
|
||||||
import { kebabCase } from 'pliny/utils/kebabCase'
|
import { kebabCase } from 'pliny/utils/kebabCase'
|
||||||
import { allBlogs } from 'contentlayer/generated'
|
import { allBlogs } from 'contentlayer/generated'
|
||||||
|
import { genPageMetadata } from 'app/seo'
|
||||||
|
|
||||||
|
export const metadata = genPageMetadata({ title: 'Tags', description: 'Things I blog about' })
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const tags = await getAllTags(allBlogs)
|
const tags = await getAllTags(allBlogs)
|
||||||
|
@ -2,7 +2,6 @@ import { ReactNode } from 'react'
|
|||||||
import type { Authors } from 'contentlayer/generated'
|
import type { Authors } from 'contentlayer/generated'
|
||||||
import SocialIcon from '@/components/social-icons'
|
import SocialIcon from '@/components/social-icons'
|
||||||
import Image from '@/components/Image'
|
import Image from '@/components/Image'
|
||||||
// import { PageSEO } from '@/components/SEO'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@ -14,7 +13,6 @@ export default function AuthorLayout({ children, content }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* <PageSEO title={`About - ${name}`} description={`About me - ${name}`} /> */}
|
|
||||||
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<div className="space-y-2 pb-8 pt-6 md:space-y-5">
|
<div className="space-y-2 pb-8 pt-6 md:space-y-5">
|
||||||
<h1 className="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
|
<h1 className="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
|
||||||
|
@ -9,7 +9,7 @@ import SectionContainer from '@/components/SectionContainer'
|
|||||||
import Image from '@/components/Image'
|
import Image from '@/components/Image'
|
||||||
import Tag from '@/components/Tag'
|
import Tag from '@/components/Tag'
|
||||||
import siteMetadata from '@/data/siteMetadata'
|
import siteMetadata from '@/data/siteMetadata'
|
||||||
// import ScrollTopAndComment from '@/components/ScrollTopAndComment'
|
import ScrollTopAndComment from '@/components/ScrollTopAndComment'
|
||||||
|
|
||||||
const editUrl = (path) => `${siteMetadata.siteRepo}/blob/master/data/${path}`
|
const editUrl = (path) => `${siteMetadata.siteRepo}/blob/master/data/${path}`
|
||||||
const discussUrl = (path) =>
|
const discussUrl = (path) =>
|
||||||
@ -37,8 +37,7 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionContainer>
|
<SectionContainer>
|
||||||
{/* <BlogSEO url={`${siteMetadata.siteUrl}/${path}`} authorDetails={authorDetails} {...content} /> */}
|
<ScrollTopAndComment />
|
||||||
{/* <ScrollTopAndComment /> */}
|
|
||||||
<article>
|
<article>
|
||||||
<div className="xl:divide-y xl:divide-gray-200 xl:dark:divide-gray-700">
|
<div className="xl:divide-y xl:divide-gray-200 xl:dark:divide-gray-700">
|
||||||
<header className="pt-6 xl:pb-6">
|
<header className="pt-6 xl:pb-6">
|
||||||
|
@ -1,89 +1,87 @@
|
|||||||
// import { useState, ReactNode } from 'react'
|
import { useState, ReactNode } from 'react'
|
||||||
// import { Comments } from 'pliny/comments'
|
import { Comments } from 'pliny/comments'
|
||||||
// import { formatDate } from 'pliny/utils/formatDate'
|
import { formatDate } from 'pliny/utils/formatDate'
|
||||||
// import { CoreContent } from 'pliny/utils/contentlayer'
|
import { CoreContent } from 'pliny/utils/contentlayer'
|
||||||
// import type { Blog } from 'contentlayer/generated'
|
import type { Blog } from 'contentlayer/generated'
|
||||||
// import Link from '@/components/Link'
|
import Link from '@/components/Link'
|
||||||
// import PageTitle from '@/components/PageTitle'
|
import PageTitle from '@/components/PageTitle'
|
||||||
// import SectionContainer from '@/components/SectionContainer'
|
import SectionContainer from '@/components/SectionContainer'
|
||||||
// // import { BlogSEO } from '@/components/SEO'
|
import siteMetadata from '@/data/siteMetadata'
|
||||||
// import siteMetadata from '@/data/siteMetadata'
|
import ScrollTopAndComment from '@/components/ScrollTopAndComment'
|
||||||
// import ScrollTopAndComment from '@/components/ScrollTopAndComment'
|
|
||||||
|
|
||||||
// interface LayoutProps {
|
interface LayoutProps {
|
||||||
// content: CoreContent<Blog>
|
content: CoreContent<Blog>
|
||||||
// children: ReactNode
|
children: ReactNode
|
||||||
// next?: { path: string; title: string }
|
next?: { path: string; title: string }
|
||||||
// prev?: { path: string; title: string }
|
prev?: { path: string; title: string }
|
||||||
// }
|
}
|
||||||
|
|
||||||
// export default function PostLayout({ content, next, prev, children }: LayoutProps) {
|
export default function PostLayout({ content, next, prev, children }: LayoutProps) {
|
||||||
// const [loadComments, setLoadComments] = useState(false)
|
const [loadComments, setLoadComments] = useState(false)
|
||||||
|
|
||||||
// const { path, slug, date, title } = content
|
const { path, slug, date, title } = content
|
||||||
|
|
||||||
// return (
|
return (
|
||||||
// <SectionContainer>
|
<SectionContainer>
|
||||||
// {/* <BlogSEO url={`${siteMetadata.siteUrl}/${path}`} {...content} /> */}
|
<ScrollTopAndComment />
|
||||||
// <ScrollTopAndComment />
|
<article>
|
||||||
// <article>
|
<div>
|
||||||
// <div>
|
<header>
|
||||||
// <header>
|
<div className="space-y-1 border-b border-gray-200 pb-10 text-center dark:border-gray-700">
|
||||||
// <div className="space-y-1 border-b border-gray-200 pb-10 text-center dark:border-gray-700">
|
<dl>
|
||||||
// <dl>
|
<div>
|
||||||
// <div>
|
<dt className="sr-only">Published on</dt>
|
||||||
// <dt className="sr-only">Published on</dt>
|
<dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400">
|
||||||
// <dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400">
|
<time dateTime={date}>{formatDate(date, siteMetadata.locale)}</time>
|
||||||
// <time dateTime={date}>{formatDate(date, siteMetadata.locale)}</time>
|
</dd>
|
||||||
// </dd>
|
</div>
|
||||||
// </div>
|
</dl>
|
||||||
// </dl>
|
<div>
|
||||||
// <div>
|
<PageTitle>{title}</PageTitle>
|
||||||
// <PageTitle>{title}</PageTitle>
|
</div>
|
||||||
// </div>
|
</div>
|
||||||
// </div>
|
</header>
|
||||||
// </header>
|
<div className="grid-rows-[auto_1fr] divide-y divide-gray-200 pb-8 dark:divide-gray-700 xl:divide-y-0">
|
||||||
// <div className="grid-rows-[auto_1fr] divide-y divide-gray-200 pb-8 dark:divide-gray-700 xl:divide-y-0">
|
<div className="divide-y divide-gray-200 dark:divide-gray-700 xl:col-span-3 xl:row-span-2 xl:pb-0">
|
||||||
// <div className="divide-y divide-gray-200 dark:divide-gray-700 xl:col-span-3 xl:row-span-2 xl:pb-0">
|
<div className="prose max-w-none pb-8 pt-10 dark:prose-dark">{children}</div>
|
||||||
// <div className="prose max-w-none pb-8 pt-10 dark:prose-dark">{children}</div>
|
</div>
|
||||||
// </div>
|
{siteMetadata.comments && (
|
||||||
// {siteMetadata.comments && (
|
<div className="pb-6 pt-6 text-center text-gray-700 dark:text-gray-300" id="comment">
|
||||||
// <div className="pb-6 pt-6 text-center text-gray-700 dark:text-gray-300" id="comment">
|
{!loadComments && (
|
||||||
// {!loadComments && (
|
<button onClick={() => setLoadComments(true)}>Load Comments</button>
|
||||||
// <button onClick={() => setLoadComments(true)}>Load Comments</button>
|
)}
|
||||||
// )}
|
{loadComments && <Comments commentsConfig={siteMetadata.comments} slug={slug} />}
|
||||||
// {loadComments && <Comments commentsConfig={siteMetadata.comments} slug={slug} />}
|
</div>
|
||||||
// </div>
|
)}
|
||||||
// )}
|
<footer>
|
||||||
// <footer>
|
<div className="flex flex-col text-sm font-medium sm:flex-row sm:justify-between sm:text-base">
|
||||||
// <div className="flex flex-col text-sm font-medium sm:flex-row sm:justify-between sm:text-base">
|
{prev && (
|
||||||
// {prev && (
|
<div className="pt-4 xl:pt-8">
|
||||||
// <div className="pt-4 xl:pt-8">
|
<Link
|
||||||
// <Link
|
href={`/${prev.path}`}
|
||||||
// href={`/${prev.path}`}
|
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
|
||||||
// className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
|
aria-label={`Previous post: ${prev.title}`}
|
||||||
// aria-label={`Previous post: ${prev.title}`}
|
>
|
||||||
// >
|
← {prev.title}
|
||||||
// ← {prev.title}
|
</Link>
|
||||||
// </Link>
|
</div>
|
||||||
// </div>
|
)}
|
||||||
// )}
|
{next && (
|
||||||
// {next && (
|
<div className="pt-4 xl:pt-8">
|
||||||
// <div className="pt-4 xl:pt-8">
|
<Link
|
||||||
// <Link
|
href={`/${next.path}`}
|
||||||
// href={`/${next.path}`}
|
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
|
||||||
// className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
|
aria-label={`Next post: ${next.title}`}
|
||||||
// aria-label={`Next post: ${next.title}`}
|
>
|
||||||
// >
|
{next.title} →
|
||||||
// {next.title} →
|
</Link>
|
||||||
// </Link>
|
</div>
|
||||||
// </div>
|
)}
|
||||||
// )}
|
</div>
|
||||||
// </div>
|
</footer>
|
||||||
// </footer>
|
</div>
|
||||||
// </div>
|
</div>
|
||||||
// </div>
|
</article>
|
||||||
// </article>
|
</SectionContainer>
|
||||||
// </SectionContainer>
|
)
|
||||||
// )
|
}
|
||||||
// }
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user