Built with thick borders, flat shadows, and zero compromises.
π Live Demo Β· π Read the Blog Β· π€ Contribute Β· π Report Bug Β· β¨ Request Feature
Most blog templates look the same β generic, forgettable, lifeless.
Coding Panda is different.
It's opinionated. It's bold. It's the blog you actually remember.
|
|
# 1. Clone & Install
git clone https://github.com/XDEV200/coding-panda-blog.git
cd coding-panda-blog
npm install
# 2. Database Setup (Supabase)
# Create a new project at supabase.com and run the schema found in:
# ./supabase/schema.sql (SQL Editor in Supabase Dashboard)
# 3. Environment Config
cp .env.example .env.local
# Update .env.local with your PROJECT_URL and ANON_KEY
# Set NEXT_PUBLIC_APP_ENV=development to use dev_ prefixed tables
# 4. Launch π
npm run devTo prevent polluting production data, this project uses a table prefixing strategy:
- Localhost: Uses tables prefixed with
dev_(e.g.,dev_posts,dev_tags). - Production: Uses original table names.
Ensure you have created the dev_ prefixed tables by duplicating the schema in your Supabase dashboard or using provided migrations.
To ensure all features work (Likes, Tags, PWA, Admin), execute the SQL in schema.sql. The essential tables are:
posts: Core blog content (title, slug, markdown, etc.)tags: Dynamic category/tag managementpost_interactions: Session-based likes and dislikesprofiles: Admin and user roles for dashboard access
graph TB
subgraph Client ["π₯οΈ Client Layer"]
A[Next.js 15 App Router] --> B[React 19 Components]
B --> C[Tailwind + Neo Design Tokens]
end
subgraph Data ["βοΈ Data Layer"]
D[Blog Service] --> E[Supabase Client]
E --> F[(Supabase PostgreSQL)]
end
subgraph Infra ["π§ Infrastructure"]
G[PWA Service Worker]
H[Security Headers]
I[Image Optimization]
end
A --> D
A --> G
A --> H
A --> I
style Client fill:#FDE047,stroke:#0A0A0A,stroke-width:3px,color:#0A0A0A
style Data fill:#60A5FA,stroke:#0A0A0A,stroke-width:3px,color:#0A0A0A
style Infra fill:#4ADE80,stroke:#0A0A0A,stroke-width:3px,color:#0A0A0A
This isn't a theme β it's a design language. Every pixel is intentional.
border: 2px solid #0A0A0AThick. Unapologetic. Every surface has them. |
box-shadow: 4px 4px 0px #0A0A0AFlat. No blur. No gradients. They move on hover. |
Heavy meets geometric. Readable at any size. |
|
| Token | Hex | Usage |
|---|---|---|
π‘ retro-yellow |
#FDE047 |
Brand, CTAs, highlights |
β« retro-black |
#0A0A0A |
Text, borders, shadows |
βͺ retro-white |
#FAFAFA |
Backgrounds, cards |
π©· retro-pink |
#F472B6 |
Category: Design |
π΅ retro-blue |
#60A5FA |
Category: Tutorials |
π’ retro-green |
#4ADE80 |
Category: Accessibility |
π retro-orange |
#FB923C |
Category: Announcements |
π£ retro-purple |
#C084FC |
Category: AI/ML |
neo β 4px 4px 0px β Default state
neo-lg β 6px 6px 0px β Featured cards
neo-xl β 8px 8px 0px β Hero elements
neo-hover β 2px 2px 0px β Active/pressed state
Every component follows the Neo design system. No ad-hoc styles.
components/
βββ ui/
β βββ Badge.tsx # 7 color-coded category pills
β βββ Button.tsx # 3 variants Γ 3 sizes with shadow animations
β βββ ThemeToggle.tsx # Light/dark mode with smooth transitions
βββ blog/
β βββ BlogCard.tsx # Standard + featured layouts
β βββ BlogGrid.tsx # Responsive grid with featured-first row
β βββ CategoryFilter.tsx # aria-pressed filter buttons
βββ layout/
βββ Navbar.tsx # Sticky yellow navbar with dynamic theme
βββ Footer.tsx # Dark footer with dynamic year
coding-panda-blog/
βββ π app/ # Next.js 15 App Router
β βββ layout.tsx # Root layout β PWA metadata, ThemeProvider
β βββ globals.css # Design tokens + Google Fonts
β βββ page.tsx # Blog listing with live category filtering
β βββ not-found.tsx # Custom 404 page
β βββ [slug]/page.tsx # Dynamic blog post route (SSG)
β βββ api/blogs/route.ts # REST API: GET /api/blogs?category=X
β
βββ π components/ # UI component library
β βββ ThemeProvider.tsx # next-themes wrapper
β βββ ui/ # Atomic design components
β βββ blog/ # Blog-specific components
β βββ layout/ # App shell (Navbar, Footer)
β
βββ π lib/ # Business logic
β βββ posts.ts # Data access layer (async, Supabase-backed)
β βββ utils.ts # Pure utilities: cn, formatDate, slugify, truncate
β
βββ π services/ # Service layer
β βββ blogService.ts # Supabase CRUD operations
β
βββ π types/ # TypeScript interfaces
β βββ blog.ts # BlogPost, BlogCategory
β
βββ π public/ # Static assets
β βββ manifest.json # PWA manifest
β βββ sw.js # Service worker (auto-generated)
β βββ icons/ # App icons (192, 512, apple-touch)
β
βββ π __tests__/ # Test suite (90%+ coverage)
βββ lib/ # Unit tests for utilities & data
βββ components/ # Component render & interaction tests
βββ services/ # Service layer tests
βββ app/ # Page & API route tests
Every request is hardened with production-grade security headers:
| Header | Value | Why |
|---|---|---|
X-Content-Type-Options |
nosniff |
Prevents MIME-type sniffing attacks |
X-Frame-Options |
DENY |
Blocks clickjacking via iframes |
X-XSS-Protection |
1; mode=block |
Legacy XSS filter activation |
Referrer-Policy |
strict-origin-when-cross-origin |
Controls referrer data leakage |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
Disables unnecessary browser APIs |
poweredByHeader |
false |
Hides Next.js fingerprint |
All external links use rel="noopener noreferrer". No information leaks.
This blog is built for everyone, not just sighted mouse-users:
- β
Semantic HTML β
header,nav,main,article,footer - β ARIA landmarks β Every navigation region is labeled
- β
aria-pressedβ Category filter buttons communicate state - β
aria-live="polite"β Post count updates announced to screen readers - β Focus-visible rings β Every interactive element has keyboard focus styles
- β Color contrast β All text meets WCAG AA contrast ratios
- β
Reduced motion β Animations respect
prefers-reduced-motion
Coding Panda works as a Progressive Web App out of the box:
| Feature | Implementation |
|---|---|
| π Offline Support | NetworkFirst caching via Workbox |
| π² Installable | Full manifest.json with icons |
| β‘ App Shortcuts | Home page shortcut in manifest |
| π¨ Theme Color | #FDE047 matches the Panda Yellow brand |
| π Auto-update | skipWaiting + register: true |
We don't guess. We test. Every component, every function, every edge case.
# Run the full test suite with coverage
npm test
# Watch mode for development
npm run test:watch
# CI mode with enforced thresholds
npm run test:ci| Metric | Threshold |
|---|---|
| Lines | β₯ 90% |
| Functions | β₯ 90% |
| Statements | β₯ 90% |
| Branches | β₯ 85% |
| Layer | Tests | What's Covered |
|---|---|---|
lib/utils.ts |
20 tests | All pure functions, edge cases, type coercion |
lib/posts.ts |
18 tests | Data integrity, async operations, error paths |
| UI Components | 40+ tests | Render, variants, a11y attributes, interactions |
| Blog Components | 20+ tests | Grid layouts, filtering, featured cards |
| Pages | 10+ tests | State management, loading states, aria-live |
| API Routes | 8+ tests | Status codes, filtering, cache headers, schema |
Returns all posts, sorted by date (newest first).
Filter by category. Available categories: design, tutorials, accessibility, announcements.
Response:
{
"posts": [
{
"slug": "building-neobrutalist-components",
"title": "Building NeoBrutalist Components",
"excerpt": "A deep dive into thick borders and flat shadows...",
"category": "tutorials",
"tags": ["react", "css", "design-system"],
"readTime": 5,
"featured": true
}
],
"total": 1
}| Technology | Version | Why This? |
|---|---|---|
| Next.js | 15 | App Router, RSC, image optimization, SSG |
| React | 19 | Latest hooks, server components support |
| TypeScript | 5.7 | Type safety across every layer |
| Tailwind CSS | 3.4 | Design tokens as config, JIT compilation |
| Supabase | 2.x | PostgreSQL + real-time subscriptions + auth |
| next-pwa | 5.6 | Zero-config PWA with Workbox |
| next-themes | 0.4 | Flicker-free dark mode |
| Lucide React | 0.473 | Consistent, tree-shakeable icons |
| date-fns | 4.1 | Modular date formatting |
| Jest | 29 | Fast, reliable test runner |
| Testing Library | 16 | User-centric component tests |
Blog posts are stored in Supabase and fetched via the blogService. Add a row to the posts table with:
{
slug: "your-post-slug",
title: "Your Post Title",
excerpt: "A brief description...",
content: "Full markdown content...",
category: "tutorials",
tags: ["react", "nextjs"],
readTime: 5,
coverColor: "#60A5FA",
featured: false
}- Add posts with the new
categorystring in Supabase - Add a colour mapping in
components/ui/Badge.tsxβCATEGORY_VARIANT_MAP - The
CategoryFilterauto-discovers categories from the data
The blogService.ts is the only file that talks to Supabase. Swap it for any backend:
blogService.ts β Your CMS / REST API / GraphQL / local MDX
β
lib/posts.ts β Same API, same types, zero component changes
β
Components β They never know the difference
| Command | Description |
|---|---|
npm run dev |
Start dev server with HMR |
npm run build |
Production build + PWA service worker |
npm start |
Start production server |
npm test |
Run all tests with coverage report |
npm run test:watch |
Tests in watch mode |
npm run test:ci |
CI tests with enforced 90% threshold |
npm run lint |
ESLint |
We love contributors! Whether you're fixing a bug, suggesting a feature, or improving documentation, your help is welcome.
Please read our Contributing Guide to get started with the development process and understand our engineering standards.
All contributors are expected to follow our Code of Conduct.
- Fork the repo
- Create your feature branch (
git checkout -b feature/awesome-feature) - Commit your changes (
git commit -m 'feat: add awesome feature') - Push to the branch (
git push origin feature/awesome-feature) - Open a Pull Request
This project is licensed under the MIT License β see the LICENSE file for details.
Built with π€ by XDEV200
If this project helped you, consider giving it a β β it means more than you think.