Content Freshness Checker
Detect last modified dates, flag stale content, check year references, schema dateModified, and update logs.
SEO
100% Free
Runs in Browser
Preview
Source Code
Toggle the controls below the code to live-edit the initial state values.
content-freshness-checker.tsx
import { useState, useMemo } from "react";
import { Textarea } from "@/components/ui/textarea";
import { Badge } from "@/components/ui/badge";
import { CheckCircle, AlertTriangle, XCircle, Clock } from "lucide-react";
function checkFreshness(html: string) {
const checks: { name: string; status: "pass" | "warn" | "fail"; detail: string }[] = [];
// Last modified
const modified = html.match(/<meta[^>]+(?:name|http-equiv)=["']last-modified["'][^>]+content=["']([^"']+)["']/i)?.[1]
|| html.match(/(?:updated|modified|published)[:\s]*([A-Z][a-z]+ \d{1,2},? \d{4}|\d{4}-\d{2}-\d{2})/i)?.[1];
if (modified) {
const date = new Date(modified);
const daysSince = Math.floor((Date.now() - date.getTime()) / (1000 * 60 * 60 * 24));
checks.push(daysSince <= 90 ? { name: "Last modified", status: "pass", detail: `${modified} (${daysSince} days ago)` }
: daysSince <= 365 ? { name: "Content aging", status: "warn", detail: `Updated ${daysSince} days ago — consider refreshing` }
: { name: "Stale content", status: "fail", detail: `Updated ${daysSince} days ago — highly recommended to update` });
} else {
checks.push({ name: "No modification date", status: "warn", detail: "Add last-modified meta or visible date" });
}
// Date references
const currentYear = new Date().getFullYear();
const yearRefs = html.match(/20\d{2}/g) || [];
const years = yearRefs.map(Number).filter(y => y >= 2015 && y <= currentYear + 1);
const latestYear = years.length > 0 ? Math.max(...years) : null;
checks.push(latestYear && latestYear >= currentYear - 1 ? { name: "Year references", status: "pass", detail: `References ${latestYear}` }
: latestYear ? { name: "Outdated year references", status: "warn", detail: `Latest year mentioned: ${latestYear}` }
: { name: "No year references", status: "warn", detail: "Consider adding a current year reference" });
// Schema dateModified
const schemaDate = html.match(/"dateModified"\s*:\s*"([^"]+)"/)?.[1];
checks.push(schemaDate ? { name: "Schema dateModified", status: "pass", detail: schemaDate }
: { name: "Missing schema dateModified", status: "warn", detail: "Add dateModified to your schema markup" });
// Content signals
const hasUpdate = /update[d]?\s*(on|:)|revision|changelog/i.test(html);
checks.push(hasUpdate ? { name: "Update changelog", status: "pass", detail: "Content mentions updates/revisions" }
: { name: "No changelog", status: "warn", detail: "Consider adding an update log for transparency" });
return checks;
}
export default function ContentFreshnessChecker() {
const [html, setHtml] = useState("");
const checks = useMemo(() => html ? checkFreshness(html) : [], [html]);
return (
<div className="space-y-4">
<Textarea value={html} onChange={(e) => setHtml(e.target.value)} className="min-h-[120px] font-mono text-xs" placeholder="Paste page HTML..." />
{checks.length > 0 && (
<div className="space-y-1.5">
{checks.map((c, i) => (
<div key={i} className="flex items-start gap-2 rounded-lg border px-3 py-2">
{c.status === "pass" ? <CheckCircle className="h-4 w-4 text-green-500 mt-0.5 shrink-0" />
: c.status === "warn" ? <AlertTriangle className="h-4 w-4 text-yellow-500 mt-0.5 shrink-0" />
: <XCircle className="h-4 w-4 text-red-500 mt-0.5 shrink-0" />}
<div><p className="text-sm">{c.name}</p><p className="text-xs text-muted-foreground">{c.detail}</p></div>
</div>
))}
<div className="rounded-lg border p-3">
<p className="text-xs text-muted-foreground"><Clock className="inline h-3 w-3 mr-1" />Recommendation: Update content every 3-6 months for best SEO results. Add a visible "Last updated" date to build trust.</p>
</div>
</div>
)}
</div>
);
}
Props Playground
1 prop
Required Libraries
Install the external dependencies used by this tool.
bun add lucide-react react
Shadcn UI Setup
Add the required Shadcn UI primitives to your project.
bun x --bun shadcn@latest add badge textarea
Shadcn UI Components
The following primitives are required in your
@/components/ui
directory.
| Component | Import Path |
|---|---|
| badge | @/components/ui/badge |
| textarea | @/components/ui/textarea |
Imports
All import statements used in this tool.
| Source | Exports |
|---|---|
| react | useState, useMemo |
| @/components/ui/textarea | Textarea |
| @/components/ui/badge | Badge |
| lucide-react | CheckCircle, AlertTriangle, XCircle, Clock |
State Management
React state variables managed within this tool.
| Variable | Initial Value |
|---|---|
| html | "" |
Variables & Constants
Constants and computed values defined in this tool.
| Name | Value |
|---|---|
| checks | [] |
| modified | html.match(/<meta[^>]+(?:name|http-equiv)=["']last-modifi... |
| date | new Date(modified) |
| daysSince | Math.floor((Date.now() - date.getTime()) / (1000 * 60 * 6... |
| currentYear | new Date().getFullYear() |
| yearRefs | html.match(/20\d{2}/g) || [] |
| years | yearRefs.map(Number).filter(y => y >= 2015 && y <= curren... |
| latestYear | years.length > 0 ? Math.max(...years) : null |
| schemaDate | html.match(/"dateModified"\s*:\s*"([^"]+)"/)?.[1] |
| hasUpdate | /update[d]?\s*(on|:)|revision|changelog/i.test(html) |
Functional Logic
Internal functions that handle the tool's core logic.
| Function | Parameters | Async |
|---|---|---|
| checkFreshness() | html: string | No |
External Resources
Documentation, tutorials, and package details for libraries used in this tool.