Merge pull request 'upstream' (#1) from upstream into main
Some checks failed
Build and Deploy docker container / build (push) Failing after 2m25s

Reviewed-on: #1
This commit is contained in:
jblu 2024-11-04 22:35:56 -06:00
commit ee6e9789bb
24 changed files with 1019 additions and 746 deletions

33
.env.example Normal file
View File

@ -0,0 +1,33 @@
# visit https://giscus.app to get your Giscus ids
NEXT_PUBLIC_GISCUS_REPO=
NEXT_PUBLIC_GISCUS_REPOSITORY_ID=
NEXT_PUBLIC_GISCUS_CATEGORY=
NEXT_PUBLIC_GISCUS_CATEGORY_ID=
NEXT_PUBLIC_UTTERANCES_REPO=
NEXT_PUBLIC_DISQUS_SHORTNAME=
MAILCHIMP_API_KEY=
MAILCHIMP_API_SERVER=
MAILCHIMP_AUDIENCE_ID=
BUTTONDOWN_API_KEY=
CONVERTKIT_API_KEY=
# curl https://api.convertkit.com/v3/forms?api_key=<your_public_api_key> to get your form ID
CONVERTKIT_FORM_ID=
KLAVIYO_API_KEY=
KLAVIYO_LIST_ID=
REVUE_API_KEY=
# Create EmailOctopus API key at https://emailoctopus.com/api-documentation
EMAILOCTOPUS_API_KEY=
# List ID can be found in the URL as a UUID after clicking a list on https://emailoctopus.com/lists
# or the settings page of your list https://emailoctopus.com/lists/{UUID}/settings
EMAILOCTOPUS_LIST_ID=
# Create Beehive API key at https://developers.beehiiv.com/docs/v2/bktd9a7mxo67n-create-an-api-key
BEEHIVE_API_KEY=
BEEHIVE_PUBLICATION_ID=

View File

@ -44,8 +44,8 @@ Internationalization support - [Template with i18n](https://tailwind-nextjs-star
- [thetalhatahir.com](https://www.thetalhatahir.com) - Talha Tahir's personal blog. Added article thumbnails, linkedIn card, Beautiful hero content, technology emoticons.
- [homing.so](https://homing.so) - Homing's personal blog about the stuff he's learning ([source code](https://github.com/hominsu/blog))
- [zS1m's Blog](https://contrails.space) - zS1m's personal blog for recording and sharing daily learning technical content ([source code](https://github.com/zS1m/nextjs-contrails))
- [dariuszwozniak.net](https://dariuszwozniak.net/) - Software development blog
- [Terminals.run](https://terminals.run) - Blog site for some thoughts and records for life and technology.
- [dariuszwozniak.net](https://dariuszwozniak.net/) - Software development blog ([source code](https://github.com/dariusz-wozniak/dariuszwozniak.net-v2))
- [dreams.plus](https://dreams.plus) - Blog site for some thoughts and records for life and technology.
- [francisaguilar.co blog](https://francisaguilar.co) - Francis Aguilar's personal blog that talks about tech, fitness, and personal development.
- [Min71 Dev Blog](https://min71.dev) - Personal blog about Blockchain, Development and etc. ([source code](https://github.com/mingi3442/blog))
- [Bryce Yu's Blog](https://earayu.github.io/) - Bryce Yu's personal Blog about distributed system, database, and web development. ([source code](https://github.com/earayu/earayu.github.io))
@ -70,6 +70,9 @@ Internationalization support - [Template with i18n](https://tailwind-nextjs-star
- [LyricsDecode.com](https://lyricsdecode.com) - A song lyrics website offering original lyrics, Romanisation, and English translations with customisable viewing options.
- [bmacharia.com](https://bmacharia.com/) - Benson Macharia's technical blog about Cybersecurity and IT Risk Management.
- [armujahid.me](https://armujahid.me/) - Abdul Rauf's personal blog about tech and random stuff.
- [leohuynh.dev](leohuynh.dev) - 🇻🇳 Leo's dev blog stories, insights, and ideas. Add `/snippets`, `/books` pages, add `ProfileCard`, `CareerTimeline` components and many more.
- [OpenSats Blog](https://opensats.org/blog) - A 501(c)(3) public charity which aims to sustainably fund free and open-source projects. ([source code](https://github.com/OpenSats/website))
- [Schedles Blog](https://schedles.com/blog) - Social media scheduling tips, strategies, and product updates for content creators. ([Project Link](https://schedles.com))
Using the template? Feel free to create a PR and add your blog to this list.
@ -82,7 +85,6 @@ Thanks to the community of users and contributors to the template! We are no lon
- [Aloisdg's cookbook](https://tambouille.vercel.app/) - with pictures and recipes!
- [GautierArcin's demo with next translate](https://tailwind-nextjs-starter-blog-seven.vercel.app/) - includes translation of mdx posts, [source code](https://github.com/GautierArcin/tailwind-nextjs-starter-blog/tree/demo/next-translate)
- [David Levai's digital garden](https://davidlevai.com/) - customized design and added email subscriptions
- [Leo's Blog](https://leohuynh.dev) - Tuan Anh Huynh's personal site. Add Snippets Page, Author Profile Card, Image Lightbox, ...
- [thvu.dev](https://thvu.dev) - Added `mdx-embed`, view count, reading minutes and more.
- [irvin.dev](https://www.irvin.dev/) - Irvin Lin's personal site. Added YouTube embedding.
- [KirillSo.com](https://www.kirillso.com/) - Personal blog & website.

View File

@ -1,6 +1,8 @@
import { NewsletterAPI } from 'pliny/newsletter'
import siteMetadata from '@/data/siteMetadata'
export const dynamic = 'force-static'
const handler = NewsletterAPI({
// @ts-ignore
provider: siteMetadata.newsletter.provider,

View File

@ -21,11 +21,10 @@ const layouts = {
PostBanner,
}
export async function generateMetadata({
params,
}: {
params: { slug: string[] }
export async function generateMetadata(props: {
params: Promise<{ slug: string[] }>
}): Promise<Metadata | undefined> {
const params = await props.params
const slug = decodeURI(params.slug.join('/'))
const post = allBlogs.find((p) => p.slug === slug)
const authorList = post?.authors || ['default']
@ -78,7 +77,8 @@ export const generateStaticParams = async () => {
return allBlogs.map((p) => ({ slug: p.slug.split('/').map((name) => decodeURI(name)) }))
}
export default async function Page({ params }: { params: { slug: string[] } }) {
export default async function Page(props: { params: Promise<{ slug: string[] }> }) {
const params = await props.params
const slug = decodeURI(params.slug.join('/'))
// Filter out drafts in production
const sortedCoreContents = allCoreContent(sortPosts(allBlogs))

View File

@ -11,7 +11,8 @@ export const generateStaticParams = async () => {
return paths
}
export default function Page({ params }: { params: { page: string } }) {
export default async function Page(props: { params: Promise<{ page: string }> }) {
const params = await props.params
const posts = allCoreContent(sortPosts(allBlogs))
const pageNumber = parseInt(params.page as string)
const initialDisplayPosts = posts.slice(

View File

@ -1,6 +1,8 @@
import { MetadataRoute } from 'next'
import siteMetadata from '@/data/siteMetadata'
export const dynamic = 'force-static'
export default function robots(): MetadataRoute.Robots {
return {
rules: {

View File

@ -2,6 +2,8 @@ import { MetadataRoute } from 'next'
import { allBlogs } from 'contentlayer/generated'
import siteMetadata from '@/data/siteMetadata'
export const dynamic = 'force-static'
export default function sitemap(): MetadataRoute.Sitemap {
const siteUrl = siteMetadata.siteUrl

View File

@ -8,7 +8,10 @@ import { genPageMetadata } from 'app/seo'
import { Metadata } from 'next'
import { notFound } from 'next/navigation'
export async function generateMetadata({ params }: { params: { tag: string } }): Promise<Metadata> {
export async function generateMetadata(props: {
params: Promise<{ tag: string }>
}): Promise<Metadata> {
const params = await props.params
const tag = decodeURI(params.tag)
return genPageMetadata({
title: tag,
@ -31,7 +34,8 @@ export const generateStaticParams = async () => {
return paths
}
export default function TagPage({ params }: { params: { tag: string } }) {
export default async function TagPage(props: { params: Promise<{ tag: string }> }) {
const params = await props.params
const tag = decodeURI(params.tag)
// Capitalize first letter and convert space to dash
const title = tag[0].toUpperCase() + tag.split(' ').join('-').slice(1)

View File

@ -16,6 +16,7 @@ export default function Footer() {
<SocialIcon kind="x" href={siteMetadata.x} size={6} />
<SocialIcon kind="instagram" href={siteMetadata.instagram} size={6} />
<SocialIcon kind="threads" href={siteMetadata.threads} size={6} />
<SocialIcon kind="medium" href={siteMetadata.medium} size={6} />
</div>
<div className="mb-2 flex space-x-2 text-sm text-gray-500 dark:text-gray-400">
<div>{siteMetadata.author}</div>

View File

@ -28,10 +28,8 @@ const Header = () => {
)}
</div>
</Link>
{/* Changed this so I can get all of my links in the header 10-19-2024. Backup Config */}
<div className="flex items-center space-x-4 leading-5 sm:space-x-6">
<div className="no-scrollbar max-w-50 hidden items-center space-x-4 overflow-x-auto sm:flex md:flex lg:flex">
{/* <div className="no-scrollbar hidden max-w-50 items-center space-x-4 overflow-x-auto sm:flex sm:space-x-6 md:max-w-72 lg:max-w-96"> */}
<div className="no-scrollbar hidden max-w-40 items-center space-x-4 overflow-x-auto sm:flex sm:space-x-6 md:max-w-72 lg:max-w-96">
{headerNavLinks
.filter((link) => link.href !== '/')
.map((link) => (

View File

@ -93,3 +93,12 @@ export function Instagram(svgProps: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function Medium(svgProps: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...svgProps}>
<title>Medium</title>
<path d="M13.54 12a6.8 6.8 0 01-6.77 6.82A6.8 6.8 0 010 12a6.8 6.8 0 016.77-6.82A6.8 6.8 0 0113.54 12zM20.96 12c0 3.54-1.51 6.42-3.38 6.42-1.87 0-3.39-2.88-3.39-6.42s1.52-6.42 3.39-6.42 3.38 2.88 3.38 6.42M24 12c0 3.17-.53 5.75-1.19 5.75-.66 0-1.19-2.58-1.19-5.75s.53-5.75 1.19-5.75C23.47 6.25 24 8.83 24 12z" />
</svg>
)
}

View File

@ -9,6 +9,7 @@ import {
Mastodon,
Threads,
Instagram,
Medium,
} from './icons'
const components = {
@ -22,6 +23,7 @@ const components = {
mastodon: Mastodon,
threads: Threads,
instagram: Instagram,
medium: Medium,
}
type SocialIconProps = {

View File

@ -18,6 +18,7 @@ import {
import rehypeSlug from 'rehype-slug'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypeKatex from 'rehype-katex'
import rehypeKatexNoTranslate from 'rehype-katex-notranslate'
import rehypeCitation from 'rehype-citation'
import rehypePrismPlus from 'rehype-prism-plus'
import rehypePresetMinify from 'rehype-preset-minify'
@ -169,6 +170,7 @@ export default makeSource({
},
],
rehypeKatex,
rehypeKatexNoTranslate,
[rehypeCitation, { path: path.join(root, 'data') }],
[rehypePrismPlus, { defaultLanguage: 'js', ignoreMissing: true }],
rehypePresetMinify,

View File

@ -80,7 +80,7 @@
}
.token.boolean {
color: rgb(138, 21, 40);
color: rgb(255, 88, 116);
}
.token.number {

View File

@ -20,6 +20,7 @@ const siteMetadata = {
linkedin: 'https://www.linkedin.com/in/jonathanbranan/',
// threads: 'https://www.threads.net',
// instagram: 'https://www.instagram.com',
// medium: 'https://medium.com',
locale: 'en-US',
// set to true if you want a navbar fixed to the top
stickyNav: false,

View File

@ -66,7 +66,7 @@ function createSearchIndex(allBlogs) {
) {
writeFileSync(
`public/${siteMetadata.search.kbarConfig.searchDocumentsPath}`,
JSON.stringify((sortPosts(allBlogs)))
JSON.stringify(sortPosts(allBlogs))
)
console.log('Local search index generated...')
}

20
faq/deploy-with-docker.md Normal file
View File

@ -0,0 +1,20 @@
# Deploy with Docker
Follow the [official Next.js repo docker build example and instructions](https://github.com/vercel/next.js/tree/canary/examples/with-docker) to deploy with docker. Copy the [`Dockerfile`](https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile) into the root of the project and modify the `next.config.js` file:
```js
// next.config.js
module.exports = {
// ... rest of the configuration.
output: 'standalone',
}
```
You can now build the docker image and run it:
```bash
docker build -t nextjs-docker .
docker run -p 3000:3000 nextjs-docker
```
Alternatively, to use docker compose, refer to the [docker compose repo](https://github.com/vercel/next.js/tree/canary/examples/with-docker-compose).

View File

@ -100,7 +100,7 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
Discuss on Twitter
</Link>
{``}
<Link href={editUrl(filePath)}>View on gitea</Link>
<Link href={editUrl(filePath)}>View on Gitea</Link>
</div>
{siteMetadata.comments && (
<div

View File

@ -1,6 +1,6 @@
{
"name": "tailwind-nextjs-starter-blog",
"version": "2.2.0",
"version": "2.3.0",
"private": true,
"scripts": {
"start": "next dev",
@ -12,29 +12,30 @@
"prepare": "husky"
},
"dependencies": {
"@headlessui/react": "1.7.19",
"@next/bundle-analyzer": "14.2.3",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.12",
"@headlessui/react": "2.2.0",
"@next/bundle-analyzer": "15.0.2",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"autoprefixer": "^10.4.13",
"body-scroll-lock": "^4.0.0-beta.0",
"contentlayer2": "0.5.1",
"contentlayer2": "0.5.3",
"esbuild": "0.20.2",
"github-slugger": "^2.0.0",
"gray-matter": "^4.0.2",
"hast-util-from-html-isomorphic": "^2.0.0",
"image-size": "1.0.0",
"next": "14.2.3",
"next-contentlayer2": "0.5.1",
"next": "15.0.2",
"next-contentlayer2": "0.5.3",
"next-themes": "^0.3.0",
"pliny": "0.2.1",
"pliny": "0.4.0",
"postcss": "^8.4.24",
"react": "18.3.1",
"react-dom": "18.3.1",
"react": "rc",
"react-dom": "rc",
"reading-time": "1.5.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-citation": "^2.0.0",
"rehype-katex": "^7.0.0",
"rehype-katex-notranslate": "^1.1.4",
"rehype-preset-minify": "7.0.0",
"rehype-prism-plus": "^2.0.0",
"rehype-slug": "^6.0.0",
@ -42,20 +43,20 @@
"remark-gfm": "^4.0.0",
"remark-github-blockquote-alert": "^1.2.1",
"remark-math": "^6.0.0",
"tailwindcss": "^3.4.3",
"tailwindcss": "^3.4.14",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@svgr/webpack": "^8.0.1",
"@types/mdx": "^2.0.12",
"@types/react": "^18.2.73",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"@typescript-eslint/eslint-plugin": "^8.12.0",
"@typescript-eslint/parser": "^8.12.0",
"cross-env": "^7.0.3",
"eslint": "^8.45.0",
"eslint-config-next": "14.2.3",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint": "^9.14.0",
"eslint-config-next": "15.0.2",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.0",
"husky": "^9.0.0",
"lint-staged": "^13.0.0",
"prettier": "^3.0.0",

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#000000</TileColor>
</tile>
</msapplication>
</browserconfig>

View File

@ -3,10 +3,12 @@ import path from 'path'
import { slug } from 'github-slugger'
import { escape } from 'pliny/utils/htmlEscaper.js'
import siteMetadata from '../data/siteMetadata.js'
import tagData from '../app/tag-data.json' with { type: 'json' }
import tagData from '../app/tag-data.json' assert { type: 'json' }
import { allBlogs } from '../.contentlayer/generated/index.mjs'
import { sortPosts } from 'pliny/utils/contentlayer.js'
const outputFolder = process.env.EXPORT ? 'out' : 'public'
const generateRssItem = (config, post) => `
<item>
<guid>${config.siteUrl}/blog/${post.slug}</guid>
@ -40,14 +42,14 @@ async function generateRSS(config, allBlogs, page = 'feed.xml') {
// RSS for blog post
if (publishPosts.length > 0) {
const rss = generateRss(config, sortPosts(publishPosts))
writeFileSync(`./public/${page}`, rss)
writeFileSync(`./${outputFolder}/${page}`, rss)
}
if (publishPosts.length > 0) {
for (const tag of Object.keys(tagData)) {
const filteredPosts = allBlogs.filter((post) => post.tags.map((t) => slug(t)).includes(tag))
const rss = generateRss(config, filteredPosts, `tags/${tag}/${page}`)
const rssPath = path.join('public', 'tags', tag)
const rssPath = path.join(outputFolder, 'tags', tag)
mkdirSync(rssPath, { recursive: true })
writeFileSync(path.join(rssPath, page), rss)
}

1594
yarn.lock

File diff suppressed because it is too large Load Diff