Modern Blog Template
A beautifully designed, responsive blog template built with Next.js, TypeScript, and Tailwind CSS. This template is specifically designed to work seamlessly with the blog generator scraper system, accepting all variable data while providing a modern, user-focused experience.
β¨ Features
π¨ Modern Design
- Responsive Design: Mobile-first approach with seamless desktop scaling
- Dark Mode Support: Automatic dark/light mode based on user preference
- Modern Typography: Beautiful typography with the Geist font family
- Smooth Animations: Subtle animations and transitions for enhanced UX
- Glassmorphism Effects: Modern glass effects on scroll and overlays
π± User Experience
- Sticky Navigation: Header with scroll effects and mobile hamburger menu
- Advanced Search & Filtering: Search articles by title, content, author, or category
- Multiple View Modes: Grid and list views for article browsing
- Reading Time Estimation: Automatic calculation of reading time
- Enhanced Article Reading: Improved typography and layout for better readability
- Related Articles: Smart related posts suggestion based on categories
π Technical Features
- TypeScript: Fully typed for better development experience
- Azure Cosmos DB (MySQL): Multi-tenant data backendβno local JSON files required
- Environment Variable Support: Runtime theming and content customization
- SEO Optimized: Meta tags, Open Graph, and Twitter Card support
- Performance Optimized: Fast loading with Next.js App Router
- Accessibility: WCAG compliant with proper ARIA labels and focus management
π― Blog Generator Integration
- Complete Data Compatibility: Accepts all scraped data variables
- Flexible Content Structure: Supports various content formats and metadata
- Theme Customization: Runtime theme customization via environment variables
- Dynamic Categories: Automatic category detection and filtering
- Author Management: Support for multiple authors with avatar generation
π Getting Started
Prerequisites
- Node.js 18+
- npm, yarn, or pnpm
Installation
git clone <repository-url>
cd blog-template
npm install
yarn install
pnpm install
- Start the development server:
npm run dev
yarn dev
pnpm dev
π Project Structure
src/
βββ app/ # Next.js App Router entry points
β βββ page.tsx # Homepage with featured articles
β βββ posts/
β β βββ page.tsx # Articles listing with search/filter
β β βββ [slug]/page.tsx # Individual article page
β βββ layout.tsx # Root layout with theme support
βββ components/ # Reusable React components
βββ lib/
βββ brand-config.ts # Runtime brand config loader + helpers
βββ content.ts # Content loading and processing
βββ db.ts # Cosmos DB connection utilities
βββ posts.ts # Queries for published posts
βββ site.ts # Shared Site/Theme/SEO types
brand.config.ts # Tenant-provided config consumed via createBlogShell
βοΈ Configuration
Site Configuration (brand.config.ts)
Every customer repository now ships a tiny config module that defines the allowed brand inputs. The config is typed so Renovate/Dependabot PRs stay safe:
import { defineBrandConfig } from "@caleblawson/blog-shell";
export default defineBrandConfig({
site: {
siteName: "Acme Widgets Blog",
logoUrl: "/brand/logo.svg",
heroTitle: "Insights from the Acme team",
heroSubtitle: "Thought leadership, case studies, and playbooks.",
heroImageUrl: "/brand/hero.png",
aboutText: "We build tools that make developers faster.",
contactEmail: "hello@acme.com",
contactPhone: "+1 (555) 123-4567",
contactAddress: "500 5th Ave, New York, NY",
theme: {
colors: {
primary: "#2563eb",
secondary: "#6366f1",
tertiary: "#06b6d4",
},
},
},
seo: {
title: "Acme Engineering Blog",
description: "Ship faster with Acme guidance.",
keywords: ["acme", "engineering", "platform"],
},
});
Store logo files under public/brand (or any folder you prefer) so each tenant commits assets alongside the config. The helper simply validates the shape and the shell runtime consumes the object through createBlogShell.
Environment Variables
Configure the following variables (all required unless noted):
# Cosmos DB for MySQL (shared multi-tenant database)
COSMOS_MYSQL_HOST="your-cosmos-host.mysql.cosmos.azure.com"
COSMOS_MYSQL_PORT="3306"
COSMOS_MYSQL_USERNAME="cosmos-user@your-cosmos-host"
COSMOS_MYSQL_PASSWORD="super-secure-password"
COSMOS_MYSQL_DATABASE="blog_platform"
# optional: provide "skip" to disable TLS validation for local proxies
COSMOS_MYSQL_SSL="required"
# optional: inline cert or relative path if you use custom CA
COSMOS_MYSQL_CA_CERT="./certs/cosmos-ca.pem"
# Tenant selector (each blog instance gets a unique tenant id)
BLOG_TENANT_ID="tenant_1234"
NEXT_PUBLIC_BLOG_TENANT_ID="tenant_1234"
# Site branding (optional overrides)
NEXT_PUBLIC_ORG_NAME="Your Organization"
NEXT_PUBLIC_ORG_LOGO_URL="https://..."
# Theme colors (optional)
NEXT_PUBLIC_PRIMARY_COLOR="#2563eb"
NEXT_PUBLIC_SECONDARY_COLOR="#6366f1"
NEXT_PUBLIC_TERTIARY_COLOR="#06b6d4"
# SEO (optional)
NEXT_PUBLIC_SEO_TITLE="Your SEO Title"
NEXT_PUBLIC_SEO_DESCRIPTION="Your SEO Description"
βΉοΈ BLOG_TENANT_ID is the authoritative value used on the server. NEXT_PUBLIC_BLOG_TENANT_ID is provided so the client bundle can reference the same tenant when needed.
Runtime Customization
Brand settings and theme colors now come from each tenant's brand.config.ts. Environment variables still win at runtime, which keeps automated deployments flexible (blue/green, previews, etc.).
Multi-Tenant Shell Package
This repository now builds the reusable @caleblawson/blog-shell package. Each customer repo stays tiny: commit a brand.config.ts, a /public/brand folder for assets, and simple wrapper files that re-export the packaged routes.
-
Install the shell
npm install @caleblawson/blog-shell
-
Create brand.config.ts using the snippet above and commit logos/icons alongside it.
-
Wire the package into your Next.js app/ directory
import brandConfig from "../brand.config";
import { createBlogShell } from "@caleblawson/blog-shell";
const { RootLayout, generateRootMetadata } = createBlogShell(brandConfig);
export const metadata = generateRootMetadata;
export default RootLayout;
import brandConfig from "../brand.config";
import { createBlogShell } from "@caleblawson/blog-shell";
const { home } = createBlogShell(brandConfig);
export const dynamic = home.dynamic;
export const revalidate = home.revalidate;
export default home.Page;
import brandConfig from "../../brand.config";
import { createBlogShell } from "@caleblawson/blog-shell";
const { postsIndex } = createBlogShell(brandConfig);
export const dynamic = postsIndex.dynamic;
export const revalidate = postsIndex.revalidate;
export default postsIndex.Page;
import brandConfig from "../../../brand.config";
import { createBlogShell } from "@caleblawson/blog-shell";
const { postDetail } = createBlogShell(brandConfig);
export const dynamic = postDetail.dynamic;
export const revalidate = postDetail.revalidate;
export const generateMetadata = postDetail.generateMetadata;
export default postDetail.Page;
Let Dependabot or Renovate watch @caleblawson/blog-shell releases so every tenant repo automatically receives PRs whenever the shared shell ships a new version.
Publishing to npm
-
Authenticate
npm login --scope=@caleblawson
Use --registry=https://registry.npmjs.org for the public npm registry or point to your private registry/GitHub Packages if desired.
-
Version the release
Update package.json (npm version patch|minor|major) so consumers receive a new semver tag. Commit and push the tag if you track releases in Git.
-
Build/test
Run npm run lint (and npm run build if you add a compile step) to make sure the package is good to go.
-
Publish
npm publish --access public
-
Notify consumers
Your Renovate/Dependabot config will auto-open PRs. If you need manual rollout, share release notes that describe the brand config/API changes.
π Content Management
Cosmos-Backed Articles
Articles are no longer stored as JSON files. Instead, every published post resides in the shared Azure Cosmos DB for MySQL instance provisioned by the blog generator:
- The
posts table is multi-tenant; rows are scoped by organization_id.
- New articles generated via the blog generator are instantly available to every deployed template instance pointing at the corresponding tenant id.
- Deleting or updating posts happens centrallyβno repository changes required.
Editor Workflow
- Generate or edit posts inside the blog generator dashboard.
- Publish themβthis writes to Cosmos DB.
- The template reads directly from Cosmos DB at runtime, automatically reflecting new content.
If you need to backfill legacy JSON posts, import them into the posts table using the schema provided in the generator repo docs. A sample migration script is included in docs/cosmos-schema.sql.
# Site branding
NEXT_PUBLIC_ORG_NAME="Your Organization"
NEXT_PUBLIC_ORG_LOGO_URL="https://..."
# Theme colors
NEXT_PUBLIC_PRIMARY_COLOR="#2563eb"
NEXT_PUBLIC_SECONDARY_COLOR="#6366f1"
NEXT_PUBLIC_TERTIARY_COLOR="#06b6d4"
# SEO
NEXT_PUBLIC_SEO_TITLE="Your SEO Title"
NEXT_PUBLIC_SEO_DESCRIPTION="Your SEO Description"
Supported Content Features
- Markdown/HTML Support: Content is stored as HTML, but Markdown can be preprocessed before insertion.
- Categories: Stored per post in metadata within Cosmos DB.
- Authors: Managed via metadata; defaults to βEditorial Teamβ when omitted.
- Images: Pass
imageUrl in metadata to render featured media.
- SEO: Individual article meta tags and social sharing ready via stored excerpt/title data.
π¨ Customization
Theme Customization
The template uses CSS custom properties for easy theming:
:root {
--primary: #2563eb;
--secondary: #6366f1;
--tertiary: #06b6d4;
}
Component Variants
PostCard component supports multiple variants:
default: Standard card layout
horizontal: Side-by-side layout for featured content
minimal: Compact list-style layout
π§ Development
Available Scripts
npm run dev
npm run build
npm run start
npm run lint
npm run type-check
Adding New Features
- New Components: Add to
src/components/
- New Pages: Add to
src/app/
- Styling: Use Tailwind classes and CSS variables
- Content: Update
src/lib/content.ts for new data structures
π Deployment
Vercel (Recommended)
- Push your code to GitHub
- Connect your repository to Vercel
- Set environment variables in Vercel dashboard
- Deploy automatically on push
Azure Container Apps (Existing Setup)
On push to main, GitHub Actions builds and deploys the app.
Required GitHub secrets now include Cosmos DB access:
AZURE_CLIENT_ID
AZURE_TENANT_ID
AZURE_SUBSCRIPTION_ID
AZURE_RESOURCE_GROUP
AZURE_CONTAINER_APP_NAME
ACR_NAME (Azure Container Registry name)
ACR_LOGIN_SERVER (e.g. myregistry.azurecr.io)
COSMOS_MYSQL_HOST
COSMOS_MYSQL_PORT
COSMOS_MYSQL_USERNAME
COSMOS_MYSQL_PASSWORD
COSMOS_MYSQL_DATABASE
BLOG_TENANT_ID
Other Platforms
The template works on any platform that supports Next.js:
- Netlify
- Railway
- DigitalOcean App Platform
- AWS Amplify
π€ Integration with Blog Generator
This template is designed to work seamlessly with the blog generator system:
- Data Compatibility: Accepts all scraped data formats
- Environment Integration: Uses environment variables for runtime customization
- Content Processing: Handles various content structures and formats
- Theme Application: Applies custom themes from scraped data
π License
MIT License - feel free to use this template for your projects.
π Acknowledgments