From b753abbf8c38f55e43b48ebf8b9388f7e41ef2ee Mon Sep 17 00:00:00 2001 From: Lee Robinson Date: Sun, 7 Apr 2024 22:00:30 -0500 Subject: [PATCH] Add blog / portfolio template. (#897) https://portfolio-blog-starter.vercel.app --- package.json | 2 +- solutions/blog/.gitignore | 37 + solutions/blog/README.md | 42 + solutions/blog/app/blog/[slug]/page.tsx | 99 ++ solutions/blog/app/blog/page.tsx | 15 + .../blog/app/blog/posts/spaces-vs-tabs.mdx | 31 + .../blog/app/blog/posts/static-typing.mdx | 52 + solutions/blog/app/blog/posts/vim.mdx | 39 + solutions/blog/app/blog/utils.ts | 90 + solutions/blog/app/components/footer.tsx | 61 + solutions/blog/app/components/mdx.tsx | 109 ++ solutions/blog/app/components/nav.tsx | 40 + solutions/blog/app/components/posts.tsx | 36 + solutions/blog/app/global.css | 150 ++ solutions/blog/app/layout.tsx | 66 + solutions/blog/app/og/route.tsx | 22 + solutions/blog/app/page.tsx | 21 + solutions/blog/app/robots.ts | 13 + solutions/blog/app/rss/route.ts | 42 + solutions/blog/app/sitemap.ts | 17 + solutions/blog/package.json | 25 + solutions/blog/pnpm-lock.yaml | 1575 +++++++++++++++++ solutions/blog/postcss.config.js | 5 + solutions/blog/tsconfig.json | 27 + 24 files changed, 2615 insertions(+), 1 deletion(-) create mode 100644 solutions/blog/.gitignore create mode 100644 solutions/blog/README.md create mode 100644 solutions/blog/app/blog/[slug]/page.tsx create mode 100644 solutions/blog/app/blog/page.tsx create mode 100644 solutions/blog/app/blog/posts/spaces-vs-tabs.mdx create mode 100644 solutions/blog/app/blog/posts/static-typing.mdx create mode 100644 solutions/blog/app/blog/posts/vim.mdx create mode 100644 solutions/blog/app/blog/utils.ts create mode 100644 solutions/blog/app/components/footer.tsx create mode 100644 solutions/blog/app/components/mdx.tsx create mode 100644 solutions/blog/app/components/nav.tsx create mode 100644 solutions/blog/app/components/posts.tsx create mode 100644 solutions/blog/app/global.css create mode 100644 solutions/blog/app/layout.tsx create mode 100644 solutions/blog/app/og/route.tsx create mode 100644 solutions/blog/app/page.tsx create mode 100644 solutions/blog/app/robots.ts create mode 100644 solutions/blog/app/rss/route.ts create mode 100644 solutions/blog/app/sitemap.ts create mode 100644 solutions/blog/package.json create mode 100644 solutions/blog/pnpm-lock.yaml create mode 100644 solutions/blog/postcss.config.js create mode 100644 solutions/blog/tsconfig.json diff --git a/package.json b/package.json index 7dac3718b4..bfb1b2ac53 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "MIT", "private": true, "engines": { - "pnpm": "8.14.2" + "pnpm": "8.15.1" }, "scripts": { "lint-staged": "lint-staged", diff --git a/solutions/blog/.gitignore b/solutions/blog/.gitignore new file mode 100644 index 0000000000..5cb102b970 --- /dev/null +++ b/solutions/blog/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem +.vscode + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/solutions/blog/README.md b/solutions/blog/README.md new file mode 100644 index 0000000000..67be6f05fa --- /dev/null +++ b/solutions/blog/README.md @@ -0,0 +1,42 @@ +# Portfolio Blog Starter + +This is a porfolio site template complete with a blog. Includes: + +- MDX and Markdown support +- Optimized for SEO (sitemap, robots, JSON-LD schema) +- RSS Feed +- Dynamic OG images +- Syntax highlighting +- Tailwind v4 +- Vercel Speed Insights / Web Analytics +- Geist font + +## Demo + +https://portfolio-blog-starter.vercel.app + +## How to Use + +You can choose from one of the following two methods to use this repository: + +### One-Click Deploy + +Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/solutions/blog&project-name=blog&repository-name=blog) + +### Clone and Deploy + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [pnpm](https://pnpm.io/installation) to bootstrap the example: + +```bash +pnpm create next-app --example https://github.com/vercel/examples/tree/main/solutions/blog blog +``` + +Then, run Next.js in development mode: + +```bash +pnpm dev +``` + +Deploy it to the cloud with [Vercel](https://vercel.com/templates) ([Documentation](https://nextjs.org/docs/app/building-your-application/deploying)). diff --git a/solutions/blog/app/blog/[slug]/page.tsx b/solutions/blog/app/blog/[slug]/page.tsx new file mode 100644 index 0000000000..70bc0081f2 --- /dev/null +++ b/solutions/blog/app/blog/[slug]/page.tsx @@ -0,0 +1,99 @@ +import type { Metadata } from 'next' +import { notFound } from 'next/navigation' +import { CustomMDX } from 'app/components/mdx' +import { formatDate, getBlogPosts } from 'app/blog/utils' +import { baseUrl } from 'app/sitemap' + +export async function generateStaticParams() { + let posts = getBlogPosts() + + return posts.map((post) => ({ + slug: post.slug, + })) +} + +export async function generateMetadata({ + params, +}): Promise { + let post = getBlogPosts().find((post) => post.slug === params.slug) + if (!post) { + return + } + + let { + title, + publishedAt: publishedTime, + summary: description, + image, + } = post.metadata + let ogImage = image ? image : `/og?title=${encodeURIComponent(title)}` + + return { + title, + description, + openGraph: { + title, + description, + type: 'article', + publishedTime, + url: `${baseUrl}/blog/${post.slug}`, + images: [ + { + url: ogImage, + }, + ], + }, + twitter: { + card: 'summary_large_image', + title, + description, + images: [ogImage], + }, + } +} + +export default function Blog({ params }) { + let post = getBlogPosts().find((post) => post.slug === params.slug) + + if (!post) { + notFound() + } + + return ( +
+