If you’re running an Astro blog in SSR mode and want to schedule posts for future publication, you might discover a challenge: date filtering happens at build time, not request time. This means posts with future dates won’t magically appear when their date arrives - they need a rebuild to go live. Here’s how I solved this problem using GitHub Actions, maintaining site performance while adding scheduling capabilities.
The Problem: Build-Time vs Runtime
When you filter posts by date in Astro:
const posts = (await getCollection("blog"))
.filter((post) => !post.data.draft && new Date(post.data.date) <= new Date())
This filtering happens when your site builds, not when users visit. In SSR mode with services like Vercel, your site is built once and cached. Future-dated posts remain hidden until the next build triggers.
The Solution Architecture
Instead of making pages fully dynamic (which would hurt performance), we’ll use GitHub Actions to trigger daily rebuilds. This approach:
- Maintains fast, cached pages between rebuilds
- Automatically publishes scheduled content
- Requires no manual intervention
- Costs nothing (GitHub Actions free tier is generous)
Step-by-Step Implementation
Step 1: Add Date Filtering to Your Content
First, ensure all your content endpoints filter out future posts. Here’s what you need to update:
Blog Listing Page (src/pages/blog/index.astro
)
const posts = (await getCollection("blog"))
.filter((post) => !post.data.draft && new Date(post.data.date) <= new Date())
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
Tag Pages (src/pages/tags/[tag].astro
)
export async function getStaticPaths() {
const posts = await getCollection("blog", ({ data }) =>
!data.draft && new Date(data.date) <= new Date()
);
const uniqueTags = [...new Set(posts.flatMap(post => post.data.tags))];
return uniqueTags.map(tag => ({
params: { tag },
props: {
posts: posts
.filter(post => post.data.tags.includes(tag))
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
}
}));
}
RSS/JSON Feeds (src/pages/rss.xml.js
, src/pages/feed.json.js
)
const posts = await getCollection("blog");
const nonDraftPosts = posts.filter(
(post) => !post.data.draft && new Date(post.data.date) <= new Date()
);
Sitemap (src/pages/sitemap.xml.ts
)
posts
.filter(post => !post.data.draft && new Date(post.data.date) <= new Date())
.forEach(post => {
urls.push({
loc: `${site}/blog/${post.slug}/`,
changefreq: 'monthly',
priority: 0.8
});
});
Step 2: Create the GitHub Action
Create .github/workflows/scheduled-rebuild.yml
:
name: Scheduled Site Rebuild
on:
schedule:
# Run at 12:05 AM Central Time (5:05 AM UTC)
- cron: '5 5 * * *'
# Allow manual trigger from GitHub Actions tab
workflow_dispatch:
jobs:
trigger-rebuild:
runs-on: ubuntu-latest
steps:
- name: Trigger Vercel Deployment
run: |
if [ -z "${{ secrets.VERCEL_DEPLOY_HOOK }}" ]; then
echo "VERCEL_DEPLOY_HOOK secret is not set"
exit 1
fi
curl -X POST "${{ secrets.VERCEL_DEPLOY_HOOK }}" \
-H "Content-Type: application/json" \
-d '{"triggered_by": "scheduled-rebuild"}' \
--fail \
--show-error
echo "✅ Deployment triggered successfully"
- name: Log Rebuild
run: |
echo "Scheduled rebuild triggered at $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "This rebuild ensures scheduled posts go live on their publication date"
Step 3: Create a Vercel Deploy Hook
- Go to your Vercel Dashboard
- Select your project
- Navigate to Settings → Git
- Scroll to Deploy Hooks
- Create a new hook:
- Name:
scheduled-rebuild
- Git Branch:
main
(or your production branch)
- Name:
- Copy the generated webhook URL
Step 4: Add the Hook to GitHub Secrets
- Go to your GitHub repository
- Navigate to Settings → Secrets and variables → Actions
- Click New repository secret
- Create the secret:
- Name:
VERCEL_DEPLOY_HOOK
- Secret: Paste the webhook URL from Vercel
- Name:
- Click Add secret
Step 5: Test Your Setup
- Go to the Actions tab in your GitHub repository
- Find Scheduled Site Rebuild workflow
- Click Run workflow → Run workflow
- Check your Vercel dashboard to confirm the deployment triggered
- Verify your scheduled posts appear after the build completes
Usage Guide
Creating Scheduled Posts
Now you can create posts with future dates:
---
title: "Announcing Our New Feature"
description: "Exciting news about our latest release"
summary: "We're launching something amazing next week"
date: "2025-12-25" # Will go live on December 25
tags: ["announcement", "features"]
draft: false # Not a draft, just scheduled
---
Your content here...
Important Distinctions
draft: true
= Never published (regardless of date)draft: false
+ future date = Published when date arrivesdraft: false
+ past date = Published immediately
Customizing Rebuild Frequency
The default schedule runs once daily at midnight Central Time. To adjust:
# Every 12 hours
- cron: '0 */12 * * *'
# Every 6 hours
- cron: '0 */6 * * *'
# Every hour (for time-sensitive content)
- cron: '0 * * * *'
# Specific times (8 AM and 5 PM Central)
- cron: '0 13 * * *' # 8 AM CT (13:00 UTC)
- cron: '0 22 * * *' # 5 PM CT (22:00 UTC)
Alternative Approaches We Considered
Making Pages Fully Dynamic
We could remove date filtering and check dates at request time:
// In your page component
const posts = posts.filter(post => {
if (typeof window !== 'undefined') {
return new Date(post.data.date) <= new Date();
}
return true;
});
Why we didn’t choose this: It would make every page request hit the server, eliminating Vercel’s edge caching and significantly impacting performance.
Using Incremental Static Regeneration (ISR)
Vercel’s ISR could revalidate pages on a schedule:
export const config = {
isr: {
expiration: 60 * 60 * 24, // 24 hours
},
};
Why we didn’t choose this: ISR in Astro is complex and doesn’t guarantee posts appear exactly when scheduled. The GitHub Actions approach is simpler and more predictable.
Manual Rebuilds
Simply triggering deploys manually when posts should go live.
Why we didn’t choose this: Defeats the purpose of scheduling. We want automation!
Monitoring and Troubleshooting
Verify Scheduled Posts
Check which posts are scheduled but not yet live:
# In your local environment
npm run dev
# Check the blog listing - scheduled posts shouldn't appear
# Check individual post URLs - they should 404 until their date
Monitor GitHub Actions
- Check the Actions tab for rebuild history
- Failed rebuilds show as ❌ with error details
- Successful rebuilds trigger Vercel deployments
Common Issues and Fixes
Posts not appearing after scheduled date?
- Verify the GitHub Action ran successfully
- Check date format: must be
"YYYY-MM-DD"
- Ensure
draft: false
in frontmatter - Manually trigger a rebuild to test
GitHub Action failing?
- Verify
VERCEL_DEPLOY_HOOK
secret is set correctly - Check if the webhook URL is valid (test with curl locally)
- Look for Vercel deployment quota issues
Time zone confusion?
- Dates in frontmatter are interpreted as midnight local time
- The rebuild runs at midnight Central Time
- Posts may appear up to 24 hours after their date depending on timing
Cost Considerations
This solution is essentially free:
- GitHub Actions: 2,000 minutes/month free (our daily rebuild uses ~1 minute/day = 30 minutes/month)
- Vercel Builds: Hobby plan includes 6,000 build minutes/month
- No additional infrastructure needed
Security Notes
The deploy hook is sensitive - anyone with it can trigger builds. GitHub Secrets are encrypted and safe, but:
- Never commit the webhook URL to your repository
- Rotate the hook periodically (delete and recreate in Vercel)
- Monitor your Vercel dashboard for unexpected deployments
Conclusion
Scheduled posts in Astro SSR don’t have to be complicated or expensive. By combining GitHub Actions with Vercel’s deploy hooks, we get a robust scheduling system that:
- Maintains optimal site performance
- Requires zero manual intervention
- Costs nothing to operate
- Scales to any posting frequency
The beauty of this approach is its simplicity. No external services, no complex state management, just a daily rebuild that makes your scheduled content appear like magic.
Now I can write posts whenever inspiration strikes and schedule them for optimal publishing times. The robots handle the rest while I sleep!
Complete Code Reference
All the code from this tutorial is available in my blog’s repository. The key files are:
.github/workflows/scheduled-rebuild.yml
- The GitHub Actionsrc/pages/blog/index.astro
- Blog listing with date filteringsrc/pages/tags/[tag].astro
- Tag pages with date filteringsrc/pages/sitemap.xml.ts
- Dynamic sitemap with date filtering
Feel free to adapt this solution for your own Astro site. Happy scheduling!