
Security News
CVE Volume Surges Past 48,000 in 2025 as WordPress Plugin Ecosystem Drives Growth
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.
@caleblawson/blog-shell
Advanced tools
A beautifully designed, responsive blog template built with Next.js, TypeScript, and Tailwind CSS. This template is specifically designed as a **reusable npm package** for the blog generator system, allowing multiple users to have their own branded blogs
A beautifully designed, responsive blog template built with Next.js, TypeScript, and Tailwind CSS. This template is specifically designed as a reusable npm package for the blog generator system, allowing multiple users to have their own branded blogs while sharing the same codebase and receiving automatic updates.
This template is designed as an npm package (@caleblawson/blog-shell) that powers multiple user blogs:
brand.config.ts file with custom colors, logos, and content@caleblawson/blog-shellbrand.config.ts file with user-specific brandinggit clone <repository-url>
cd blog-template
npm install
# or
yarn install
# or
pnpm install
npm run dev
# or
yarn dev
# or
pnpm dev
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
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.
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_IDis the authoritative value used on the server.NEXT_PUBLIC_BLOG_TENANT_IDis provided so the client bundle can reference the same tenant when needed.
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.).
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
// app/layout.tsx
import brandConfig from "../brand.config";
import { createBlogShell } from "@caleblawson/blog-shell";
const { RootLayout, generateRootMetadata } = createBlogShell(brandConfig);
export const metadata = generateRootMetadata;
export default RootLayout;
// app/page.tsx
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;
// app/posts/page.tsx
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;
// app/posts/[slug]/page.tsx
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.
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 # or --access restricted for private scopes
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.
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:
posts table is multi-tenant; rows are scoped by organization_id.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"
imageUrl in metadata to render featured media.The template uses CSS custom properties for easy theming:
:root {
--primary: #2563eb;
--secondary: #6366f1;
--tertiary: #06b6d4;
/* ... more variables */
}
PostCard component supports multiple variants:
default: Standard card layouthorizontal: Side-by-side layout for featured contentminimal: Compact list-style layoutnpm run dev # Start development server
npm run build # Build for production
npm run start # Start production server
npm run lint # Run ESLint
npm run type-check # Run TypeScript checks
src/components/src/app/src/lib/content.ts for new data structuresOn push to main, GitHub Actions builds and deploys the app.
Required GitHub secrets now include Cosmos DB access:
AZURE_CLIENT_IDAZURE_TENANT_IDAZURE_SUBSCRIPTION_IDAZURE_RESOURCE_GROUPAZURE_CONTAINER_APP_NAMEACR_NAME (Azure Container Registry name)ACR_LOGIN_SERVER (e.g. myregistry.azurecr.io)COSMOS_MYSQL_HOSTCOSMOS_MYSQL_PORTCOSMOS_MYSQL_USERNAMECOSMOS_MYSQL_PASSWORDCOSMOS_MYSQL_DATABASEBLOG_TENANT_IDThe template works on any platform that supports Next.js:
This template is designed to work seamlessly with the blog generator system:
MIT License - feel free to use this template for your projects.
FAQs
Reusable blog shell package for multi-tenant blog generation
We found that @caleblawson/blog-shell demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.

Security News
Socket CEO Feross Aboukhadijeh joins Insecure Agents to discuss CVE remediation and why supply chain attacks require a different security approach.

Security News
Tailwind Labs laid off 75% of its engineering team after revenue dropped 80%, as LLMs redirect traffic away from documentation where developers discover paid products.