Implementing Automatic Date Management for Astro Paper Blog Posts
I kept forgetting to update the pubDatetime and modDatetime fields in the frontmatter of my blog posts. The result was inconsistent date formats, missing modification dates, and the same boring chore every time I edited something. So I automated it.
This post walks through the setup: a Git pre-commit hook plus a couple of Node.js scripts that handle publication and modification dates without any manual intervention.
Table of contents
Open Table of contents
The Problem
Before automating this, the workflow had a handful of issues:
Inconsistent date formats
Different posts used different formats — some 2025-06-08, some 2025-06-08T19:40:00Z. That inconsistency leaks into JSON-LD and RSS output.
Forgotten updates
New posts would ship without pubDatetime. Existing posts almost never got modDatetime updated. Manual = forgotten.
SEO impact
Search engines use publication and modification dates as freshness signals. Missing or malformed dates are easy to fix and worth fixing.
Friction
Stopping to look up “what’s the ISO format again” every time I edit a post is the kind of micro-interruption that adds up.
The Solution
Three pieces, all working off the same script:
1. Git pre-commit hook
- Detects modified blog posts during a commit
- Updates dates before the commit is finalized
- Re-stages the updated files
- Plays nicely with the existing lint-staged setup
2. Date update script
- Parses frontmatter
- Sets
pubDatetimefor new posts, updatesmodDatetimefor existing ones - Writes ISO 8601 timestamps consistently
- Preserves all other frontmatter fields
3. CLI for new post creation
- Interactive prompt for new posts with dates pre-filled
- Manual date update commands when you need them
- Force-update for bulk operations
Architecture
graph TD
A[Developer Creates/Edits Post] --> B{Git Commit}
B --> C[Pre-commit Hook Triggered]
C --> D[Date Update Script Runs]
D --> E{New Post?}
E -->|Yes| F[Set pubDatetime]
E -->|No| G[Update modDatetime]
F --> H[Stage Updated File]
G --> H
H --> I[Continue with Commit]
I --> J[Lint-staged Runs]
J --> K[Commit Completed]
L[Manual Post Creation] --> M[npm run new-post]
M --> N[Interactive CLI]
N --> O[Generate Post with Dates]
The whole thing is invisible during normal use — you commit, the hook runs, dates are right.
Implementation
File Structure
├── scripts/
│ ├── update-post-dates.js # Core date update automation
│ └── new-post.js # New post creation with dates
├── .husky/pre-commit # Enhanced Git pre-commit hook
├── package.json # Updated with new scripts
└── docs/DATE_MANAGEMENT.md # Comprehensive documentation
Core Components
1. Date Update Script (scripts/update-post-dates.js)
Frontmatter parsing:
function parseFrontmatter(content) {
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
const match = content.match(frontmatterRegex);
// Parse YAML-like frontmatter into JavaScript object
// Handle arrays, strings, booleans, and nested structures
}
Date update logic:
function updatePostDates(filePath, isNewPost = false) {
const now = generateISODate(); // 2025-06-09T02:41:38.032Z
if (isNewPost || !frontmatter.pubDatetime) {
frontmatter.pubDatetime = now;
} else {
frontmatter.modDatetime = now;
}
}
Git integration:
function isFileModified(filePath, gitStatus) {
const relativePath = path.relative(process.cwd(), filePath);
return gitStatus.some(line =>
line.includes(relativePath) &&
(line.startsWith('M ') || line.startsWith('A '))
);
}
2. New Post Creator (scripts/new-post.js)
Two interfaces — interactive prompt or flags.
Interactive:
npm run new-post
# Prompts for title, description, tags, etc.
Flags:
npm run new-post "Post Title" --tags "tag1,tag2" --featured --published
Frontmatter scaffold:
const frontmatter = {
title,
author: 'John Lam',
pubDatetime: generateISODate(),
slug: generateSlug(title),
featured: false,
draft: true,
tags: ['others'],
description: `A blog post about ${title}`
};
3. Git Pre-Commit Hook (.husky/pre-commit)
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# Update blog post dates automatically
echo "Updating blog post dates..."
node scripts/update-post-dates.js --pre-commit
# Run lint-staged
npx lint-staged
Dates are updated before lint-staged runs, so formatting picks up the new frontmatter.
Usage
Automatic
Just commit normally — the hook handles the rest:
# 1. Create or edit a blog post
vim src/content/blog/my-new-post.md
# 2. Stage and commit as usual
git add src/content/blog/my-new-post.md
git commit -m "Add new blog post about Kubernetes"
What happens during the commit:
- Pre-commit hook detects the modified
.mdfile - Script determines whether it’s a new post or modification
- Updates
pubDatetime(new) ormodDatetime(existing) - Stages the updated file
- Continues with the normal commit
Manual
For more control:
New posts
# Interactive
npm run new-post
# Title only
npm run new-post "My New Blog Post"
# Full options
node scripts/new-post.js \
--title "Advanced Kubernetes Networking" \
--description "Deep dive into CNI plugins and network policies" \
--tags "kubernetes,networking,devops" \
--author "John Lam" \
--featured \
--published
Existing posts
# Update dates for all modified posts
npm run update-dates
# Force update all posts (use with caution)
npm run update-dates:force
# Update a specific file
node scripts/update-post-dates.js src/content/blog/specific-post.md
What This Buys You
Developer experience
- No more remembering to update dates
- Git workflow is unchanged
- Updated files staged automatically
Consistency
- All dates use the same ISO format:
2025-06-09T02:41:38.032Z - Matches the Astro content collections schema
- UTC throughout
Smart detection
- Distinguishes new posts from edits
- Only touches files that actually changed
- Other frontmatter fields are preserved exactly
SEO
- Accurate publication dates for indexing
- Modification dates as freshness signals
- Clean structured data output
Compatibility
- Works alongside existing Husky / lint-staged setup
- Pure Node.js, no extra runtime dependencies
- Cross-platform
Testing
A few representative scenarios:
New post creation
node scripts/new-post.js --title "Test Automatic Date Management" \
--description "Testing the new system" \
--tags "testing,automation" --published
Result — frontmatter generated with the current ISO timestamp:
---
title: Test Automatic Date Management
author: John Lam
pubDatetime: 2025-06-09T02:34:37.846Z
slug: test-automatic-date-management
featured: false
draft: false
tags:
- testing
- automation
description: Testing the new automatic date management system
---
Pre-commit hook
Modifying the test post and committing produced this output:
Updating blog post dates...
Astro Paper Date Manager
============================
Running in pre-commit mode...
Found 7 blog post files
Processing: test-automatic-date-management.md
Updated modification date
Staged updated file for commit
Summary:
Files processed: 1
Files updated: 1
The resulting frontmatter picked up modDatetime correctly:
---
title: Test Automatic Date Management
author: John Lam
pubDatetime: 2025-06-09T02:35:13.241Z
modDatetime: 2025-06-09T02:35:36.099Z # ← Automatically added
slug: test-automatic-date-management
# ... rest of frontmatter
---
Edge cases verified
- Posts without an existing
pubDatetime - Posts with malformed frontmatter (graceful failure with a clear error)
- No-op when there are no changes
- Non-blog markdown ignored
- Binary and other formats skipped
Integration
- Astro content collection validation still passes
- SEO structured data unchanged
- RSS feed sorting still works
- Build process unaffected
- Existing lint-staged workflow intact
Conclusion
The setup is small — one hook, two scripts — but it removes a recurring source of frontmatter bugs and makes modification dates actually reliable. If you’re running Astro Paper or any similar setup with frontmatter dates, the same pattern transfers cleanly.
Source is in the Astro Paper repo under scripts/ and .husky/. Feel free to lift it.