An AI-powered, auto-publishing personal blog built with Next.js 15, Sanity CMS, Convex, and Google Gemini. The platform autonomously generates and publishes blog articles on a daily schedule — complete with AI-written content, cover images, source citations, and structured metadata — while also supporting traditional manual authoring through Sanity Studio.
- Automated daily article publishing via Convex cron jobs that trigger at a configurable schedule
- Topic-driven generation — schedule topics in advance via the Convex dashboard with optional custom instructions
- Google Gemini 2.5 Flash for article content generation with Google Search grounding for factual accuracy and inline source citations
- AI-generated cover images using Gemini's image generation capabilities
- Automatic title & excerpt extraction from generated content using structured JSON output
- Source attribution — grounding chunks from Google Search are stored and displayed alongside each post
- Sanity Studio embedded at
/studiofor a native, customizable authoring experience - Real-time visual editing with Sanity Presentation Tool for side-by-side live preview
- Draft Mode with seamless toggling for content preview before publishing
- Block content support via Portable Text with Markdown-to-block conversion for AI-generated content
- Unsplash integration for easy image sourcing and hotspot/crop controls
- Sanity AI Assist preconfigured for image alt text generation
- Dark/Light/System theme toggle using
next-themeswith smooth transitions - Responsive design with Tailwind CSS v4 and Inter font from Google Fonts
- Paginated post listing with "See More" progressive loading
- Hero post layout with cover image, author avatar, date, and excerpt
- Typography powered by
@tailwindcss/typographyandshadcn-prosewith full dark mode support - shadcn/ui components (Button, Dropdown Menu) for polished, accessible UI elements
- Static generation with Incremental Static Revalidation (ISR) — no rebuild delays for new content
- Vercel Speed Insights integrated for performance monitoring
- Dynamic Open Graph metadata per post with cover image support
- Structured SEO metadata generation from Sanity settings (title, description, author, OG images)
- Next.js Turbopack enabled for fast local development
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Next.js 15 │────▶│ Sanity CMS │◀────│ Convex Backend │
│ (Frontend) │ │ (Content Lake) │ │ (Serverless) │
│ │ │ │ │ │
│ • App Router │ │ • Posts │ │ • Cron Jobs │
│ • SSG + ISR │ │ • Authors │ │ • Actions │
│ • Draft Mode │ │ • Settings │ │ • Queries │
│ • Sanity Studio│ │ • Images │ │ • Scheduler │
└─────────────────┘ └─────────────────┘ └────────┬────────┘
│
▼
┌─────────────────┐
│ Google Gemini │
│ (AI) │
│ │
│ • Content Gen │
│ • Image Gen │
│ • Grounding │
│ • Extraction │
└─────────────────┘
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router, Turbopack) |
| Language | TypeScript 5.6 |
| CMS | Sanity v3 (Content Lake, Studio, Presentation Tool) |
| Backend | Convex (Serverless functions, cron jobs, database) |
| AI | Google Gemini (@google/genai) — 2.5 Flash + Image Gen |
| Styling | Tailwind CSS v4, shadcn/ui, shadcn-prose |
| Theming | next-themes |
| Validation | Zod v4 |
| Testing | Vitest, convex-test |
| Deployment | Vercel |
| Package Manager | Yarn 4 (Berry) |
personal-blog/
├── app/
│ ├── (blog)/ # Blog route group
│ │ ├── layout.tsx # Root blog layout (theme, footer, draft mode)
│ │ ├── page.tsx # Home page (hero post + more stories)
│ │ ├── posts/
│ │ │ ├── page.tsx # Paginated "All Stories" listing
│ │ │ └── [slug]/ # Individual post pages
│ │ └── actions.ts # Server actions (draft mode toggle)
│ ├── (sanity)/ # Sanity Studio route group
│ │ └── studio/ # Embedded Sanity Studio at /studio
│ ├── api/
│ │ └── draft-mode/ # Draft mode API routes
│ ├── globals.css # Global styles, CSS variables, dark mode
│ └── icon.tsx # Dynamic favicon generation
│
├── components/
│ ├── alert-banner.tsx # Draft mode notification banner
│ ├── article.tsx # Post card component
│ ├── avatar.tsx # Author avatar
│ ├── cover-image.tsx # Post cover image with Sanity CDN
│ ├── date.tsx # Date formatting component
│ ├── footer.tsx # Site footer with Portable Text
│ ├── mode-toggle.tsx # Dark/Light/System theme switcher
│ ├── more-stories.tsx # Additional posts grid
│ ├── onboarding.tsx # First-run onboarding guide
│ ├── paginated-stories.tsx # Paginated post listing with "See More"
│ ├── portable-text.tsx # Portable Text renderer
│ ├── providers/
│ │ ├── convex-client-provider.tsx
│ │ └── theme-provider.tsx
│ └── ui/ # shadcn/ui primitives (Button, Dropdown)
│
├── config/
│ └── metadata.config.ts # Dynamic SEO metadata generation
│
├── convex/ # Convex backend
│ ├── _config/
│ │ └── env.config.ts # Environment variable exports
│ ├── _constants/
│ │ └── prompts.ts # AI system prompts & response schemas
│ ├── _lib/
│ │ ├── google.genai.ts # Gemini AI integration (content, images, extraction)
│ │ ├── sanity.ts # Sanity write client (create/update posts)
│ │ └── sanity.test.ts # Sanity integration tests
│ ├── actions.ts # Convex actions (generate article, upload, etc.)
│ ├── crons.ts # Daily article generation cron job
│ ├── jobs.ts # Job orchestration (topic lookup → scheduled generation)
│ ├── queries.ts # Convex queries (get today's topics)
│ └── schema.ts # Convex database schema (topics table)
│
├── sanity/
│ ├── lib/
│ │ ├── api.ts # Sanity project config (projectId, dataset, etc.)
│ │ ├── client.ts # Sanity client instance
│ │ ├── demo.ts # Demo/fallback content
│ │ ├── fetch.ts # Sanity fetch wrapper with caching
│ │ ├── queries.ts # GROQ queries (hero, posts, pagination, etc.)
│ │ ├── token.ts # Read token access
│ │ └── utils.ts # URL resolvers, OG image helpers
│ ├── plugins/
│ │ ├── assist.ts # AI Assist plugin with preset prompts
│ │ └── settings.tsx # Singleton document structure plugin
│ └── schemas/
│ ├── documents/
│ │ ├── author.ts # Author document schema
│ │ └── post.ts # Post document schema (with sources field)
│ └── singletons/
│ └── settings.tsx # Site-wide settings singleton
│
├── sanity.config.ts # Sanity Studio configuration
├── tailwind.config.ts # Tailwind CSS v4 configuration
├── next.config.ts # Next.js configuration
├── vitest.config.mts # Vitest test configuration
└── package.json
- Node.js ≥ 18
- Yarn 4 (Berry) — managed via
corepack - A Sanity account and project
- A Convex account and project
- A Google AI Studio API key
git clone git@github.com:agnivon/personal-blog.git
cd personal-blogcorepack enable
yarn installCopy the example environment file:
cp .env.local.example .env.localFill in all the required values in .env.local:
| Variable | Description |
|---|---|
NEXT_PUBLIC_SANITY_PROJECT_ID |
Your Sanity project ID |
NEXT_PUBLIC_SANITY_DATASET |
Sanity dataset name (e.g., production) |
SANITY_API_READ_TOKEN |
Sanity API token with Viewer permissions |
SANITY_API_DEVELOPER_TOKEN |
Sanity API token with Editor/Admin permissions (for Convex write operations) |
SANITY_BLOG_AUTHOR_ID |
Sanity document _id of the default blog author |
CONVEX_DEPLOYMENT |
Your Convex deployment identifier |
NEXT_PUBLIC_CONVEX_URL |
Your Convex deployment URL |
GEMINI_API_KEY |
Google AI Studio API key |
TIMEZONE |
Timezone for date operations (default: Asia/Kolkata) |
DATE_FORMAT_STRING |
Date format string (default: dd/MM/yyyy) |
CONVEX_ENV |
development or production (controls cron activation) |
- Go to manage.sanity.io and select your project.
- Navigate to the 🔌 API tab.
- Click + Add API token.
- Create a Viewer token for
SANITY_API_READ_TOKEN(used for live preview). - Create a Developer/Editor token for
SANITY_API_DEVELOPER_TOKEN(used by Convex to create posts).
If you're setting up a brand new Sanity project:
yarn setupNote: When asked "Would you like to add configuration files for a Sanity project in this Next.js folder?", answer No — the configuration files are already included.
yarn devThis concurrently starts:
- Next.js (with Turbopack) at http://localhost:3000
- Convex dev server syncing functions to your development deployment
Access Sanity Studio at http://localhost:3000/studio.
- Open Sanity Studio at
/studio - Click + Create → Post
- Fill in the title, generate a slug, write content, add a cover image, and select an author
- Click Publish to make the post live
- Open your Convex Dashboard
- Navigate to the Data tab and open the
topicstable - Add a document with:
topic— the subject for the article (e.g., "The Future of WebAssembly")instructions— (optional) custom instructions for the AIgenerationDate— the date to generate indd/MM/yyyyformat
- The daily cron job (at 05:30 UTC / 11:00 AM IST) will:
- Query topics scheduled for today
- Generate article content using Gemini with Google Search grounding
- Extract the title and excerpt
- Generate a cover image
- Upload the complete post to Sanity
Note: The cron job only runs when
CONVEX_ENV=production. In development, you can manually trigger article generation via the Convex dashboard.
- In development, content is shown immediately
- In production, new content uses Time-based Revalidation (up to ~60 seconds delay)
- Toggle draft mode at
/api/draft-mode/enableor via the Vercel Toolbar
Run the test suite with Vitest:
# Run tests in watch mode
yarn test
# Run tests once
yarn test:once
# Run tests with coverage
yarn test:coverage
# Debug tests
yarn test:debug| Script | Description |
|---|---|
yarn dev |
Start Next.js + Convex dev servers concurrently |
yarn next-dev |
Start only the Next.js dev server (with Turbopack) |
yarn convex-dev |
Start only the Convex dev server |
yarn build |
Build the Next.js production bundle |
yarn start |
Start the Next.js production server |
yarn lint |
Run ESLint |
yarn typegen |
Extract Sanity schema & generate TypeScript types |
yarn setup |
Initialize Sanity project configuration |
yarn test |
Run Vitest in watch mode |
yarn test:once |
Run tests once |
yarn test:coverage |
Run tests with coverage report |
yarn convex-deploy |
Deploy Convex functions to production |
- Push your code to GitHub
- Import the project on Vercel
- Add all environment variables from
.env.localto the Vercel project settings - Deploy
After deployment, link your local project:
npx vercel linkDeploy your Convex functions to production:
yarn convex-deployMake sure CONVEX_ENV is set to production on your Convex deployment for the daily cron job to activate.
This project is licensed under the MIT License - see the LICENSE file for details.