-
+
)
}
-
-export async function getStaticProps() {
- // Get static blog posts
- const staticPostsData = getSortedPosts({ directory: '_blog', runner: '** BLOG PAGE **' })
-
- // Only static data at build time; CMS posts are fetched client-side
- const allPostsData = [...staticPostsData].sort((a: any, b: any) => {
- const dateA = a.date ? new Date(a.date).getTime() : new Date(a.formattedDate).getTime()
- const dateB = b.date ? new Date(b.date).getTime() : new Date(b.formattedDate).getTime()
- return dateB - dateA
- })
-
- // Generate RSS feed from combined posts
- const rss = generateRss(allPostsData)
-
- // create a rss feed in public directory
- // rss feed is added via component in render return
- fs.writeFileSync('./public/rss.xml', rss)
-
- // generate a series of rss feeds for each author (for PlanetPG)
- const planetPgPosts = allPostsData.filter((post: any) => post.tags?.includes('planetpg'))
- const planetPgAuthors = planetPgPosts.map((post: any) => post.author.split(','))
- const uniquePlanetPgAuthors = new Set([].concat(...planetPgAuthors))
-
- uniquePlanetPgAuthors.forEach((author) => {
- const authorPosts = planetPgPosts.filter((post: any) => post.author.includes(author))
- if (authorPosts.length > 0) {
- const authorRss = generateRss(authorPosts, author)
- fs.writeFileSync(`./public/planetpg-${author}-rss.xml`, authorRss)
- }
- })
-
- return {
- props: {
- blogs: allPostsData,
- },
- // revalidate: 60 * 10, // Revalidate every 10 minutes
- }
-}
-
-export default Blog
diff --git a/apps/www/app/blog/[slug]/BlogPostClient.tsx b/apps/www/app/blog/[slug]/BlogPostClient.tsx
new file mode 100644
index 00000000000..aed4ce25857
--- /dev/null
+++ b/apps/www/app/blog/[slug]/BlogPostClient.tsx
@@ -0,0 +1,135 @@
+'use client'
+
+import dynamic from 'next/dynamic'
+import { NextSeo } from 'next-seo'
+import { useState, useMemo } from 'react'
+import { useLivePreview } from '@payloadcms/live-preview-react'
+
+import authors from 'lib/authors.json'
+import { CMS_SITE_ORIGIN } from 'lib/constants'
+import { isNotNullOrUndefined } from 'lib/helpers'
+
+import type { Blog, BlogData, CMSAuthor, PostReturnType, ProcessedBlogData, Tag } from 'types/post'
+
+const BlogPostRenderer = dynamic(() => import('components/Blog/BlogPostRenderer'))
+
+type BlogPostPageProps = {
+ prevPost: PostReturnType | null
+ nextPost: PostReturnType | null
+ relatedPosts: (PostReturnType & BlogData)[]
+ blog: Blog & BlogData
+ isDraftMode: boolean
+}
+
+export default function BlogPostClient(props: BlogPostPageProps) {
+ const isDraftMode = props.isDraftMode
+ const [previewData] = useState
(props.blog)
+ const shouldUseLivePreview = isDraftMode && (props.blog as any).isCMS
+
+ const { data: livePreviewData, isLoading: isLivePreviewLoading } = useLivePreview({
+ initialData: props.blog,
+ serverURL: CMS_SITE_ORIGIN || 'http://localhost:3030',
+ depth: 2,
+ })
+
+ const blogMetaData = useMemo(() => {
+ if (isDraftMode && shouldUseLivePreview) {
+ if (livePreviewData && typeof livePreviewData === 'object') {
+ return { ...props.blog, ...livePreviewData }
+ }
+ if (previewData !== props.blog) {
+ return previewData
+ }
+ }
+ return props.blog
+ }, [isDraftMode, shouldUseLivePreview, livePreviewData, previewData, props.blog]) as any
+
+ const isCMS = (blogMetaData as any).isCMS
+ const imageUrl = isCMS
+ ? (blogMetaData as any).thumb ?? ''
+ : (blogMetaData as any).thumb
+ ? `/images/blog/${(blogMetaData as any).thumb}`
+ : ''
+
+ const meta = {
+ title: (blogMetaData as any).meta_title ?? (blogMetaData as any).title,
+ description: (blogMetaData as any).meta_description ?? (blogMetaData as any).description,
+ url: `https://supabase.com/blog/${(blogMetaData as any).slug}`,
+ }
+
+ const processTag = (tag: any): string => {
+ return typeof tag === 'string' ? tag : tag.name
+ }
+
+ const tags = (blogMetaData as any).tags
+ ? Array.isArray((blogMetaData as any).tags)
+ ? ((blogMetaData as any).tags as Tag[]).map(processTag)
+ : []
+ : []
+
+ const blogAuthors = isCMS
+ ? ((blogMetaData as any).authors as CMSAuthor[]) || []
+ : ((blogMetaData as any).author as string)
+ ?.split(',')
+ .map((authorId: string) => {
+ const foundAuthor = authors.find((author) => author.author_id === authorId)
+ return foundAuthor
+ ? {
+ author: foundAuthor.author || 'Author',
+ author_image_url: foundAuthor.author_image_url || null,
+ author_url: foundAuthor.author_url || '#',
+ position: foundAuthor.position || '',
+ }
+ : null
+ })
+ .filter(isNotNullOrUndefined) || []
+
+ const authorUrls = (blogAuthors as any)
+ .map((author: any) => author?.author_url)
+ .filter(isNotNullOrUndefined)
+
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/apps/www/app/blog/[slug]/page.tsx b/apps/www/app/blog/[slug]/page.tsx
new file mode 100644
index 00000000000..43560007608
--- /dev/null
+++ b/apps/www/app/blog/[slug]/page.tsx
@@ -0,0 +1,135 @@
+import { draftMode } from 'next/headers'
+import { getAllPostSlugs, getPostdata, getSortedPosts } from 'lib/posts'
+import { getAllCMSPostSlugs, getCMSPostBySlug } from 'lib/get-cms-posts'
+
+import type { Blog, BlogData, PostReturnType } from 'types/post'
+
+import BlogPostClient from '~/app/blog/[slug]/BlogPostClient'
+
+type MatterReturn = {
+ data: BlogData
+ content: string
+}
+
+type BlogPostPageProps = {
+ prevPost: PostReturnType | null
+ nextPost: PostReturnType | null
+ relatedPosts: (PostReturnType & BlogData)[]
+ blog: Blog & BlogData
+ isDraftMode: boolean
+}
+
+type Params = {
+ slug: string
+}
+
+export async function generateStaticParams() {
+ const staticPaths = getAllPostSlugs('_blog')
+ const cmsPaths = await getAllCMSPostSlugs()
+ return [...staticPaths, ...cmsPaths].map((p) => ({ slug: p.params.slug }))
+}
+
+export default async function BlogPostPage({ params }: { params: Params }) {
+ if (!params?.slug) {
+ throw new Error('Missing slug for app/blog/[slug]/page.tsx')
+ }
+
+ const slug = `${params.slug}`
+ const { isEnabled: isDraft } = await draftMode()
+
+ const matter = (await import('gray-matter')).default
+ const { mdxSerialize } = await import('lib/mdx/mdxSerialize')
+
+ try {
+ const postContent = await getPostdata(slug, '_blog')
+ const parsedContent = matter(postContent) as unknown as MatterReturn
+ const content = parsedContent.content
+ const tocDepth = (parsedContent.data as any)?.toc_depth ?? 2
+ const mdxSource = await mdxSerialize(content, { tocDepth })
+ const blogPost = { ...parsedContent.data }
+
+ const allStaticPosts = getSortedPosts({ directory: '_blog' })
+ const allPosts = [...allStaticPosts].sort((a, b) => {
+ const aDate = new Date((a as unknown as { date: string }).date).getTime()
+ const bDate = new Date((b as unknown as { date: string }).date).getTime()
+ return bDate - aDate
+ })
+ const currentIndex = allPosts.findIndex((post) => post.slug === slug)
+ const nextPost = currentIndex === allPosts.length - 1 ? null : allPosts[currentIndex + 1]
+ const prevPost = currentIndex === 0 ? null : allPosts[currentIndex - 1]
+ const tocResult = (mdxSource as any).scope?.toc || { content: '' }
+ const processedContent = tocResult.content
+ const relatedPosts = getSortedPosts({
+ directory: '_blog',
+ limit: 3,
+ tags: (mdxSource as { scope: { tags?: string[] } }).scope.tags,
+ currentPostSlug: slug,
+ }) as unknown as (BlogData & PostReturnType)[]
+
+ const props: BlogPostPageProps = {
+ prevPost,
+ nextPost,
+ relatedPosts,
+ blog: {
+ ...(blogPost as any),
+ content: mdxSource,
+ toc: {
+ ...tocResult,
+ content: processedContent,
+ },
+ } as any,
+ isDraftMode: isDraft,
+ }
+
+ return
+ } catch {}
+
+ const cmsPost = await getCMSPostBySlug(slug, isDraft)
+
+ if (!cmsPost) {
+ if (isDraft) {
+ const publishedPost = await getCMSPostBySlug(slug, false)
+ if (!publishedPost) return null
+ const mdxSource = await mdxSerialize(publishedPost.content || '')
+ const props: BlogPostPageProps = {
+ prevPost: null,
+ nextPost: null,
+ relatedPosts: [],
+ blog: {
+ ...publishedPost,
+ tags: publishedPost.tags || [],
+ authors: publishedPost.authors || [],
+ isCMS: true,
+ content: mdxSource,
+ toc: publishedPost.toc,
+ image: publishedPost.image ?? undefined,
+ thumb: publishedPost.thumb ?? undefined,
+ } as any,
+ isDraftMode: isDraft,
+ }
+ return
+ }
+ return null
+ }
+
+ const mdxSource = await mdxSerialize(cmsPost.content || '')
+
+ const props: BlogPostPageProps = {
+ prevPost: null,
+ nextPost: null,
+ relatedPosts: [],
+ blog: {
+ ...cmsPost,
+ tags: cmsPost.tags || [],
+ authors: cmsPost.authors || [],
+ isCMS: true,
+ content: mdxSource,
+ toc: cmsPost.toc,
+ image: cmsPost.image ?? undefined,
+ thumb: cmsPost.thumb ?? undefined,
+ } as any,
+ isDraftMode: isDraft,
+ }
+
+ return
+}
diff --git a/apps/www/pages/blog/categories/[category].tsx b/apps/www/app/blog/categories/[category]/page.tsx
similarity index 60%
rename from apps/www/pages/blog/categories/[category].tsx
rename to apps/www/app/blog/categories/[category]/page.tsx
index 1abfdd0621d..d37149c40b2 100644
--- a/apps/www/pages/blog/categories/[category].tsx
+++ b/apps/www/app/blog/categories/[category]/page.tsx
@@ -2,37 +2,24 @@ import { NextSeo } from 'next-seo'
import { getSortedPosts, getAllCategories } from '~/lib/posts'
import Link from 'next/link'
import { startCase } from 'lodash'
-
import DefaultLayout from '~/components/Layouts/Default'
import BlogGridItem from '~/components/Blog/BlogGridItem'
import type PostTypes from '~/types/post'
-export async function getStaticProps({ params }: any) {
- const posts = getSortedPosts({ directory: '_blog', limit: 0, categories: [params.category] })
- return {
- props: {
- category: params.category,
- blogs: posts,
- },
- }
-}
+type Params = { category: string }
-export async function getStaticPaths() {
+export async function generateStaticParams() {
const categories = getAllCategories('_blog')
- return {
- paths: categories.map((category: any) => ({ params: { category: category } })),
- fallback: false,
- }
+ return categories.map((category: string) => ({ category }))
}
-interface Props {
- category: string
- blogs: PostTypes[]
-}
-
-function CategoriesIndex(props: Props) {
- const { blogs, category } = props
- const capitalizedCategory = startCase(category.replaceAll('-', ' '))
+export default async function CategoriesPage({ params }: { params: Params }) {
+ const blogs = getSortedPosts({
+ directory: '_blog',
+ limit: 0,
+ categories: [params.category],
+ }) as unknown as PostTypes[]
+ const capitalizedCategory = startCase(params.category.replaceAll('-', ' '))
return (
<>
@@ -46,14 +33,14 @@ function CategoriesIndex(props: Props) {
Blog
/
- {`${capitalizedCategory}`}
+ {capitalizedCategory}