├── .eslintrc.json ├── .github ├── open-blog-card-list.png ├── open-blog-demo.gif ├── open-blog-read.png ├── open-blog-search.png ├── open-blog-tag.png ├── open-blog.png └── open-blog.svg ├── .gitignore ├── README.md ├── app ├── author │ └── [slug] │ │ └── page.tsx ├── fonts │ ├── GeistMonoVF.woff │ └── GeistVF.woff ├── global-error.tsx ├── globals.css ├── layout.tsx ├── not-found.tsx ├── page.tsx ├── read │ └── [slug] │ │ └── page.tsx ├── search │ └── page.tsx └── tags │ └── [slug] │ └── page.tsx ├── components.json ├── components ├── Card.tsx ├── Footer.tsx ├── Header.tsx ├── Hero.tsx ├── Newsletter.tsx ├── Pagination.tsx ├── icons │ ├── facebook.tsx │ ├── github.tsx │ ├── index.ts │ ├── instagram.tsx │ ├── linkedin.tsx │ └── twitter.tsx ├── theme │ ├── theme-provider.tsx │ └── theme-toggle.tsx ├── ui │ ├── badge.tsx │ ├── button.tsx │ ├── card.tsx │ ├── dropdown-menu.tsx │ ├── input.tsx │ ├── label.tsx │ ├── navigation-menu.tsx │ └── pagination.tsx └── useFuse.ts ├── data ├── index.ts └── posts.ts ├── lib └── utils.ts ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── favicon.ico └── images │ ├── android.jpg │ ├── blog-2.jpg │ ├── camera.jpg │ ├── computer.jpg │ ├── health.jpg │ ├── ios.jpg │ ├── photographer.jpg │ ├── smartphone.jpg │ ├── software-developer.jpg │ ├── technology.jpg │ └── vegetables.jpg ├── tailwind.config.js ├── tsconfig.json └── type.d.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "@next/next/no-img-element": "off", 5 | "react/no-unescaped-entities": "off", 6 | "@next/next/no-html-link-for-pages": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.github/open-blog-card-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontendweb3/open-blog/d540397b36a02860c8ce4ac58ab80dbd3a3ce3a3/.github/open-blog-card-list.png -------------------------------------------------------------------------------- /.github/open-blog-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontendweb3/open-blog/d540397b36a02860c8ce4ac58ab80dbd3a3ce3a3/.github/open-blog-demo.gif -------------------------------------------------------------------------------- /.github/open-blog-read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontendweb3/open-blog/d540397b36a02860c8ce4ac58ab80dbd3a3ce3a3/.github/open-blog-read.png -------------------------------------------------------------------------------- /.github/open-blog-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontendweb3/open-blog/d540397b36a02860c8ce4ac58ab80dbd3a3ce3a3/.github/open-blog-search.png -------------------------------------------------------------------------------- /.github/open-blog-tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontendweb3/open-blog/d540397b36a02860c8ce4ac58ab80dbd3a3ce3a3/.github/open-blog-tag.png -------------------------------------------------------------------------------- /.github/open-blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontendweb3/open-blog/d540397b36a02860c8ce4ac58ab80dbd3a3ce3a3/.github/open-blog.png -------------------------------------------------------------------------------- /.github/open-blog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The Open Blog is a open source template is design and built with Next.js 15, Shadcn UI and tailwind CSS. 2 | 3 | Take a look at the demo of the Minimalist template! 4 | 5 | ## Demo 6 | 7 | [![Demo](/.github/open-blog-demo.gif)](https://open-blog-beta.vercel.app/) 8 | 9 | ## Our Template Stack 10 | 11 | ![Next JS](https://img.shields.io/badge/Next-black?style=for-the-badge&logo=next.js&logoColor=white) 12 | ![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white) 13 | ![Markdown](https://img.shields.io/badge/markdown-%23000000.svg?style=for-the-badge&logo=markdown&logoColor=white) 14 | ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) 15 | ![Shadcn UI](https://img.shields.io/badge/Shadcn/ui-white.svg?&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+c2hhZGNuL3VpPC90aXRsZT48cGF0aCBkPSJNMjIuMjE5IDExLjc4NCAxMS43ODQgMjIuMjE5Yy0uNDA3LjQwNy0uNDA3IDEuMDY4IDAgMS40NzYuNDA3LjQwNyAxLjA2OC40MDcgMS40NzYgMEwyMy42OTUgMTMuMjZjLjQwNy0uNDA4LjQwNy0xLjA2OSAwLTEuNDc2LS40MDgtLjQwNy0xLjA2OS0uNDA3LTEuNDc2IDBaTTIwLjEzMi4zMDUuMzA1IDIwLjEzMmMtLjQwNy40MDctLjQwNyAxLjA2OCAwIDEuNDc2LjQwOC40MDcgMS4wNjkuNDA3IDEuNDc2IDBMMjEuNjA4IDEuNzgxYy40MDctLjQwNy40MDctMS4wNjggMC0xLjQ3Ni0uNDA4LS40MDctMS4wNjktLjQwNy0xLjQ3NiAwWiIvPjwvc3ZnPg==) 16 | 17 | ## Pictures 18 | 19 | ![Home](./.github/open-blog.png) 20 | 21 | ![Reading Page](./.github/open-blog-read.png) 22 | 23 | ![Tag Page](./.github/open-blog-tag.png) 24 | 25 | ![Search Page](./.github/open-blog-search.png) 26 | 27 | ![Card List](./.github/open-blog-card-list.png) 28 | 29 | 30 | ## Clone Repository 31 | 32 | First, download the Next.js open blog template code from the GitHub repository using the following command: 33 | 34 | ```bash 35 | git clone git clone https://github.com/frontendweb3/open-blog.git 36 | ``` 37 | Then follow the below **steps**: 38 | 39 | ```markdown 40 | # step 1 41 | cd open-blog 42 | 43 | # step 2 44 | pnpm install 45 | # or 46 | yarn install 47 | 48 | # step 3 49 | yarn dev 50 | # or 51 | pnpm dev 52 | ``` 53 | 54 | 55 | ### Our primary stack for the Open Blog template includes: 56 | 57 | 1. Next.js 58 | 2. TypeScript 59 | 3. PNPM 60 | 4. Shadcn UI 61 | 5. Tailwind CSS 62 | 63 | ## Additional packages: 64 | 65 | 1. Lucide icons 66 | 2. @tailwindcss/typography 67 | 3. fuse.js 68 | 69 | After downloading, open your browser and navigate to [http://localhost:3000](http://localhost:3000) to see the result. 70 | -------------------------------------------------------------------------------- /app/author/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from "@/components/Card"; 2 | import { GetAuthors, GetAuthorsPost, } from "@/data"; 3 | import type { Posts } from "@/type"; 4 | import type { Metadata } from 'next' 5 | import { notFound } from "next/navigation"; 6 | 7 | export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise { 8 | const { slug } = await params 9 | return { title: `Published By ${slug?.trim().replaceAll(" ", "-")}` } 10 | } 11 | 12 | 13 | export async function generateStaticParams() { 14 | return GetAuthors() 15 | } 16 | 17 | 18 | export default async function Page({ params }: { params: Promise<{ slug: string }> }) { 19 | const slug = (await params).slug 20 | const posts = await GetAuthorsPost(slug) 21 | if (posts.length === 0) { 22 | notFound() 23 | } 24 | return ( 25 | <> 26 |
27 | 28 |

29 | Articles Published By {slug.toLowerCase().trim().split("-").join(" ")} 30 |

31 |
32 | 33 |
34 | 35 | { 36 | posts.map((item: Posts) => ) 37 | } 38 | 39 |
40 | 41 | 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontendweb3/open-blog/d540397b36a02860c8ce4ac58ab80dbd3a3ce3a3/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontendweb3/open-blog/d540397b36a02860c8ce4ac58ab80dbd3a3ce3a3/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /app/global-error.tsx: -------------------------------------------------------------------------------- 1 | 'use client' // Error boundaries must be Client Components 2 | import { Button } from "@/components/ui/button" 3 | import { House } from "lucide-react" 4 | import Link from "next/link" 5 | 6 | export default function GlobalError({ 7 | error, 8 | reset, 9 | }: { 10 | error: Error & { digest?: string } 11 | reset: () => void 12 | }) { 13 | return ( 14 | 15 | 16 |
17 |
18 |
19 |

{error.name}

20 |

{error.message}

21 | 22 | 23 | 24 |
25 |
26 |
27 | 28 | 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 20 14.3% 4.1%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 20 14.3% 4.1%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 20 14.3% 4.1%; 13 | --primary: 24 9.8% 10%; 14 | --primary-foreground: 60 9.1% 97.8%; 15 | --secondary: 60 4.8% 95.9%; 16 | --secondary-foreground: 24 9.8% 10%; 17 | --muted: 60 4.8% 95.9%; 18 | --muted-foreground: 25 5.3% 44.7%; 19 | --accent: 60 4.8% 95.9%; 20 | --accent-foreground: 24 9.8% 10%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 60 9.1% 97.8%; 23 | --border: 20 5.9% 90%; 24 | --input: 20 5.9% 90%; 25 | --ring: 20 14.3% 4.1%; 26 | --chart-1: 12 76% 61%; 27 | --chart-2: 173 58% 39%; 28 | --chart-3: 197 37% 24%; 29 | --chart-4: 43 74% 66%; 30 | --chart-5: 27 87% 67%; 31 | --radius: 0.5rem 32 | } 33 | .dark { 34 | --background: 20 14.3% 4.1%; 35 | --foreground: 60 9.1% 97.8%; 36 | --card: 20 14.3% 4.1%; 37 | --card-foreground: 60 9.1% 97.8%; 38 | --popover: 20 14.3% 4.1%; 39 | --popover-foreground: 60 9.1% 97.8%; 40 | --primary: 60 9.1% 97.8%; 41 | --primary-foreground: 24 9.8% 10%; 42 | --secondary: 12 6.5% 15.1%; 43 | --secondary-foreground: 60 9.1% 97.8%; 44 | --muted: 12 6.5% 15.1%; 45 | --muted-foreground: 24 5.4% 63.9%; 46 | --accent: 12 6.5% 15.1%; 47 | --accent-foreground: 60 9.1% 97.8%; 48 | --destructive: 0 62.8% 30.6%; 49 | --destructive-foreground: 60 9.1% 97.8%; 50 | --border: 12 6.5% 15.1%; 51 | --input: 12 6.5% 15.1%; 52 | --ring: 24 5.7% 82.9%; 53 | --chart-1: 220 70% 50%; 54 | --chart-2: 160 60% 45%; 55 | --chart-3: 30 80% 55%; 56 | --chart-4: 280 65% 60%; 57 | --chart-5: 340 75% 55% 58 | } 59 | } 60 | @layer base { 61 | * { 62 | @apply border-border; 63 | } 64 | body { 65 | @apply bg-background text-foreground; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import localFont from "next/font/local"; 3 | import "./globals.css"; 4 | import { Footer } from "@/components/Footer"; 5 | import { Header } from "@/components/Header"; 6 | import { ThemeProvider } from "@/components/theme/theme-provider" 7 | 8 | const geistSans = localFont({ 9 | src: "./fonts/GeistVF.woff", 10 | variable: "--font-geist-sans", 11 | weight: "100 900", 12 | }); 13 | const geistMono = localFont({ 14 | src: "./fonts/GeistMonoVF.woff", 15 | variable: "--font-geist-mono", 16 | weight: "100 900", 17 | }); 18 | 19 | export const metadata: Metadata = { 20 | title: "Home | Open Blog", 21 | description: "The Open Blog is a open source template is design and built with Next.js 15, Shadcn UI and tailwind CSS.", 22 | }; 23 | 24 | export default function RootLayout({ 25 | children, 26 | }: Readonly<{ 27 | children: React.ReactNode; 28 | }>) { 29 | return ( 30 | 31 | 32 | 38 |
39 | {children} 40 |