diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..95c9fd0 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,94 @@ +# Sample workflow for building and deploying a Next.js site to GitHub Pages +# +# To get started with Next.js see: https://nextjs.org/docs/getting-started +# +name: Deploy Next.js site to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect package manager + id: detect-package-manager + run: | + if [ -f "${{ github.workspace }}/yarn.lock" ]; then + echo "manager=yarn" >> $GITHUB_OUTPUT + echo "command=install" >> $GITHUB_OUTPUT + echo "runner=yarn" >> $GITHUB_OUTPUT + exit 0 + elif [ -f "${{ github.workspace }}/package.json" ]; then + echo "manager=npm" >> $GITHUB_OUTPUT + echo "command=ci" >> $GITHUB_OUTPUT + echo "runner=npx --no-install" >> $GITHUB_OUTPUT + exit 0 + else + echo "Unable to determine package manager" + exit 1 + fi + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + cache: ${{ steps.detect-package-manager.outputs.manager }} + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Restore cache + uses: actions/cache@v4 + with: + path: | + .next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- + + - name: Install dependencies + run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} --force + + - name: Build with Next.js + run: ${{ steps.detect-package-manager.outputs.runner }} next build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./out + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26b002a --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for commiting if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/app/blog/[slug]/blog-content.tsx b/app/blog/[slug]/blog-content.tsx new file mode 100644 index 0000000..bf4cf2f --- /dev/null +++ b/app/blog/[slug]/blog-content.tsx @@ -0,0 +1,180 @@ +"use client" + +import { useEffect, useState } from 'react' +import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote' +import { serialize } from 'next-mdx-remote/serialize' +import rehypePrism from 'rehype-prism-plus' +import remarkGfm from 'remark-gfm' +import { notFound } from 'next/navigation' +import { Clock, User } from 'lucide-react' +import { type BlogPost } from '@/lib/blog-data' + +interface BlogContentProps { + initialPost: BlogPost + slug: string +} + +export default function BlogContent({ initialPost, slug }: BlogContentProps) { + const [mdxSource, setMdxSource] = useState(null) + const [post] = useState(initialPost) + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + async function loadPost() { + try { + const response = await fetch(`/blogs/${slug}.md`) + if (!response.ok) { + throw new Error('Blog post not found') + } + + const text = await response.text() + const mdxSource = await serialize(text, { + mdxOptions: { + remarkPlugins: [remarkGfm], + rehypePlugins: [rehypePrism], + } + }) + + setMdxSource(mdxSource) + } catch (error) { + console.error('Failed to load blog post:', error) + notFound() + } finally { + setIsLoading(false) + } + } + + loadPost() + }, [slug]) + + if (isLoading) { + return + } + + if (!post || !mdxSource) { + return notFound() + } + + const components = { + pre: ({ className, ...props }: React.HTMLAttributes) => ( +
+    ),
+    code: ({ className, ...props }: React.HTMLAttributes) => (
+      
+    ),
+    h1: ({ className, ...props }: React.HTMLAttributes) => (
+      

+ ), + h2: ({ className, ...props }: React.HTMLAttributes) => ( +

+ ), + h3: ({ className, ...props }: React.HTMLAttributes) => ( +

+ ), + p: ({ className, ...props }: React.HTMLAttributes) => ( +

+ ), + ul: ({ className, ...props }: React.HTMLAttributes) => ( +