Hashnode Blog Integration
Integrate Hashnode for blogging
Overview
Portfolio OS integrates with Hashnode as the primary content source for blog posts. This guide explains how the integration works, how to configure it, and how to troubleshoot common issues.
Architecture
Content Flow
Hashnode GraphQL API → Content API Layer → Blog Pages
↓
Dashboard API (Optional)
Key Components
-
Hashnode API Client (
apps/site/lib/hashnode-api.ts)- Direct GraphQL queries to Hashnode
- Handles all blog content fetching
- Supports posts, publication info, and metadata
-
Content API Layer (
apps/site/lib/content-api.ts)- Unified interface for blog content
- Automatic fallback from Dashboard API to Hashnode
- Fast fail detection for unavailable services
-
Blog Pages (
apps/site/app/blog/)/blog- Main blog listing page/blog/[slug]- Individual blog post pages- Static generation with ISR (Incremental Static Regeneration)
Configuration
Environment Variables
Add these to apps/site/.env.local:
# Hashnode Configuration (Required)
NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST=mindware.hashnode.dev
NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT=https://gql.hashnode.com
# Dashboard API Configuration (Optional - for future dashboard integration)
DASHBOARD_API_URL=http://localhost:3001
DASHBOARD_API_SECRET=your-api-secret-here
Required Variables
NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST: Your Hashnode publication subdomain- Example:
mindware.hashnode.devoryourblog.hashnode.dev - Get this from your Hashnode publication settings
- Example:
Optional Variables
DASHBOARD_API_URL: URL of your Dashboard API (for dual-source setup)DASHBOARD_API_SECRET: Authentication token for Dashboard API
Features
1. Static Site Generation (SSG)
Blog posts are pre-rendered at build time for optimal performance:
// apps/site/app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const slugs = await getAllPostSlugs();
return slugs.map((slug) => ({ slug }));
}
Benefits:
- âš¡ Instant page loads
- 🔠Better SEO
- 💰 Lower serverless costs
2. Incremental Static Regeneration (ISR)
Pages automatically revalidate every 60 seconds:
export const revalidate = 60;
Benefits:
- 📰 Fresh content without rebuilding
- 🚀 Fast performance maintained
- âš™ï¸ Automatic updates
3. Dynamic Rendering for New Posts
New posts published after build are automatically rendered:
export const dynamicParams = true;
Benefits:
- ✅ No 404s for new posts
- 🔄 Seamless content updates
- 📱 Works with Hashnode webhooks
4. Dual-Source Content Strategy
The integration supports both Hashnode and a Dashboard API:
-
Primary: Dashboard API (if available)
- Custom analytics
- Draft previews
- Advanced features
-
Fallback: Hashnode API (always available)
- Published content
- Production reliability
- Fast performance
How It Works
Blog Post Flow
-
User visits
/blog/my-post-slug -
Next.js checks if page is pre-rendered
- ✅ Yes → Serve cached HTML instantly
- ⌠No → Dynamic render (if
dynamicParams = true)
-
Content API fetches post
const post = await fetchPostBySlug(slug); -
Fast-fail Dashboard check (2s timeout)
- ✅ Dashboard available → Use Dashboard API
- ⌠Dashboard unavailable → Use Hashnode API
-
Hashnode GraphQL query
query PostBySlug($host: String!, $slug: String!) { publication(host: $host) { post(slug: $slug) { id title brief slug content { html markdown } # ... more fields } } } -
Page renders with post data
- SEO metadata
- Open Graph tags
- Twitter cards
- Full content
ISR Revalidation
Every 60 seconds, when a cached page is requested:
- Serve cached version immediately
- Background: Re-fetch from Hashnode
- Update cache if content changed
- Next request gets fresh content
Publishing Workflow
From Hashnode
-
Write & publish on Hashnode
- Use Hashnode's editor
- Publish when ready
-
Content appears on your site
- Immediately via ISR (within 60s)
- Or rebuild for instant update
From Dashboard (Future)
- Write in Dashboard editor
- Enable "Publish to Hashnode"
- Dashboard syncs to Hashnode
- Content appears on site
Troubleshooting
404 Errors on Blog Posts
Symptoms:
- Blog listing page works
- Individual posts show 404
Solutions:
-
Verify Hashnode configuration
cd apps/site npx tsx scripts/test-hashnode-connection.ts -
Check environment variables
# In apps/site/.env.local NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST=your-blog.hashnode.dev -
Rebuild the site
pnpm build -
Check if generateStaticParams is working
- Look for build output: "â—‹ /blog/[slug]"
- Should show list of pre-rendered pages
Slow Page Loads
Symptoms:
- First load is slow
- Subsequent loads are fast
Solutions:
-
Dashboard API timeout
- If Dashboard is slow/down, increase timeout
- Or disable Dashboard API check
-
Hashnode API issues
- Check Hashnode status
- Verify GraphQL endpoint: https://gql.hashnode.com/
-
ISR not working
- Check
revalidatesetting - Verify build output
- Check
Content Not Updating
Symptoms:
- Published new post on Hashnode
- Not showing on site
Solutions:
-
Wait for ISR (60 seconds)
- Visit the blog listing page
- Wait 1 minute
- Refresh
-
Clear Next.js cache
rm -rf apps/site/.next pnpm build -
Verify post is published on Hashnode
- Check https://mindware.hashnode.dev
- Ensure post is public
-
Trigger manual revalidation
- Visit:
/api/revalidate?secret=YOUR_SECRET&path=/blog
- Visit:
Testing
Test Hashnode Connection
Run the test script to verify everything works:
cd apps/site
npx tsx scripts/test-hashnode-connection.ts
Expected Output:
✅ Publication found!
Title: John Schibelli
Total Posts: 20
✅ Found 10 posts!
1. My First Post
Slug: my-first-post
URL: https://johnschibelli.dev/blog/my-first-post
Manual Testing
-
Visit blog listing
http://localhost:3000/blog -
Click on a post
- Should load without 404
- Content should display
-
Check browser DevTools
- Look for Content API logs
- Verify Hashnode is being used
Production Testing
-
Deploy to Vercel
-
Check build logs
- Look for "Generating static pages"
- Verify all blog slugs are listed
-
Test on production URL
https://yoursite.com/blog https://yoursite.com/blog/your-post-slug
Performance Optimization
Build Time
- Fast: Uses ISR, not full SSG
- Scalable: Handles 100+ posts
- Efficient: Only fetches slugs at build time
Runtime
- < 100ms: Cached pages (ISR)
- < 500ms: Dashboard API
- < 1s: Hashnode API
- 2s timeout: Dashboard failover
Caching Strategy
// Next.js Data Cache
{
revalidate: 60, // 60 second cache
tags: ['blog-posts'] // Cache invalidation
}
API Reference
Content API Functions
fetchPosts(first?, after?)
Fetch multiple posts with pagination.
const posts = await fetchPosts(10); // Get 10 posts
fetchPostBySlug(slug)
Fetch a single post by slug.
const post = await fetchPostBySlug('my-post');
fetchPublication()
Fetch publication metadata.
const pub = await fetchPublication();
getAllPostSlugs()
Get all post slugs for static generation.
const slugs = await getAllPostSlugs();
// ['post-1', 'post-2', ...]
Hashnode GraphQL Queries
All queries are in apps/site/lib/hashnode-api.ts:
PostsByPublication- Fetch posts listPostBySlug- Fetch single postPublication- Fetch publication infoAllPostSlugs- Fetch all slugs
Security Considerations
Public Environment Variables
NEXT_PUBLIC_HASHNODE_PUBLICATION_HOSTis public (in browser)- This is safe - it's just your publication URL
- No API keys needed for reading public posts
Private Environment Variables
DASHBOARD_API_SECRETshould never be exposed- Only used server-side
- Rotate if compromised
Migration Guide
From Static JSON Files
- Import posts to Hashnode
- Update environment variables
- Remove static JSON files
- Rebuild site
From WordPress
- Use Hashnode's WordPress importer
- Verify all posts imported
- Update environment variables
- Test thoroughly
From Other CMS
- Export to Markdown
- Import to Hashnode via API
- Update configuration
- Test and deploy
Best Practices
Content Strategy
-
Draft in Hashnode
- Use Hashnode's editor
- Preview before publishing
-
SEO Optimization
- Write compelling titles
- Use cover images
- Add meta descriptions
- Tag posts appropriately
-
Publishing Schedule
- Publish during peak hours
- Use Hashnode's scheduling
- Monitor ISR revalidation
Development
-
Local Development
- Use real Hashnode data
- Test with
.env.local - Never commit secrets
-
Staging
- Use separate Hashnode publication
- Test ISR behavior
- Verify build performance
-
Production
- Monitor build times
- Set up error tracking
- Cache invalidation strategy
Monitoring
Key Metrics
- Build Time: Should be < 2 minutes
- Page Load: Should be < 1 second
- ISR Updates: Check 60s revalidation
- 404 Rate: Should be near 0%
Logging
Enable detailed logs in development:
// Look for these in console:
[Content API] Fetching post by slug: my-post
[Hashnode API] Successfully fetched post: my-post
Error Tracking
Use Sentry or similar:
try {
const post = await fetchPostBySlug(slug);
} catch (error) {
Sentry.captureException(error);
throw error;
}
Support
Getting Help
- Check this documentation
- Run test script
- Check Hashnode status
- Review server logs
- Ask in Discord/Slack
Common Resources
Changelog
2025-10-08
- Added
generateStaticParamsfor blog posts - Implemented ISR with 60s revalidation
- Added dynamic params support
- Improved error handling and logging
- Created comprehensive documentation
Future Enhancements
- Webhook support for instant updates
- Full Dashboard API integration
- Advanced caching strategies
- Comment system integration
- Analytics integration