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.