如何用 TypeScript 建立以證據為先的 SEO 審核佇列
Building an Evidence-First SEO Audit Queue in TypeScript
文章示範如何用 TypeScript 建立 SEO 分流流程,將公司配對、技術觀察和商業判斷分開,並以可重現的證據篩選審核候選項目。系統只把符合條件的結果交由人工評估,不將偵測到的問題當作合格潛在客戶。LLM 可協助把技術觀察改寫成審核備註,但摘要必須以原始證據為準。
A Lighthouse score or a missing meta description can be useful diagnostic information. It is not, by itself, a reason to contact a business.
For an agency, the more useful unit of work is an evidence-backed review candidate: a real company, a reasonably matched website, a reproducible finding, and enough context for a person to decide whether a conversation would make sense.
This post builds a small TypeScript triage layer for that workflow. The code is intentionally illustrative, not a production crawler or a lead-generation shortcut.
1. Keep identity, evidence, and decisions separate
A good pipeline should avoid collapsing three very different questions:
- Entity matching: Does this website probably belong to the company being considered?
- Technical observation: Can we reproduce a particular issue on a particular URL?
- Commercial judgment: Would reviewing this issue be useful for an agency and its potential client?
The third question should remain a human decision. An automatically detected issue is not proof that someone needs a service or wants to be contacted.
Here are minimal data contracts:
type CompanyCandidate = {
legalStatus: "active" | "inactive";
website: string;
matchSignals: string[];
};
type Finding = {
url: string;
kind: "noindex" | "broken_internal_link" | "missing_title";
checkedAt: string; // ISO 8601
reproducible: boolean;
pageRole: "core" | "other";
evidence: string;
};
type Decision =
| { status: "reject"; reason: string }
| { status: "review"; priority: "high" | "normal"; reason: string };
The matchSignals array might represent independent public clues, such as consistent branding and a first-party company reference. It is not an ownership-verification certificate. A website can look related to a company without legally belonging to it.
2. Make the first gate intentionally conservative
A simple helper normalises hostnames:
function host(url: string): string {
return new URL(url).hostname
.toLowerCase()
.replace(/^www\./, "");
}
Then triage each finding:
function triage(
company: CompanyCandidate,
finding: Finding,
now = Date.now()
): Decision {
if (company.legalStatus !== "active") {
return { status: "reject", reason: "Company is not active" };
}
// A sample threshold, not a guarantee of verified ownership.
if (company.matchSignals.length < 2) {
return { status: "reject", reason: "Domain match needs review" };
}
try {
if (host(company.website) !== host(finding.url)) {
return { status: "reject", reason: "Different hostname" };
}
} catch {
return { status: "reject", reason: "Invalid URL" };
}
const ageDays = (now - Date.parse(finding.checkedAt)) / 86_400_000;
if (!Number.isFinite(ageDays) || ageDays < 0 || ageDays > 14) {
return { status: "reject", reason: "Refresh the observation" };
}
if (!finding.reproducible || !finding.evidence.trim()) {
return { status: "reject", reason: "Missing reproducible evidence" };
}
const highPriority =
finding.kind === "noindex" && finding.pageRole === "core";
return {
status: "review",
priority: highPriority ? "high" : "normal",
reason: "Evidence is ready for human assessment",
};
}
Notice the return value is review, not "qualified lead." That distinction matters.
A few production caveats:
- Strict hostname equality rejects legitimate cross-domain and subdomain arrangements. Real systems need carefully reviewed aliases and ownership evidence.
- Two signals do not necessarily mean two independent signals. Deduplicate correlated sources.
- A 14-day freshness threshold is an example. The right interval depends on the finding and site volatility.
- An HTTP response alone may be insufficient. JavaScript rendering, canonicals, robots directives, redirects, and page intent can change the interpretation.
3. Make every finding explainable
A reviewer should be able to answer:
- Which exact URL was checked?
- What did the system observe?
- When was it last checked?
- Can someone reproduce it?
- Why might that page matter?
For example, a noindex directive on a public service landing page is worth checking. The same directive on an account settings page might be entirely intentional.
Store the actual observed directive and response context as evidence. Don't replace them with a broad statement like "your website is losing customers." That claim requires a level of business evidence most crawlers do not have.
An illustrative input:
const company: CompanyCandidate = {
legalStatus: "active",
website: "https://example.org",
matchSignals: ["brand-match", "first-party-company-reference"],
};
const finding: Finding = {
url: "https://example.org/services",
kind: "noindex",
checkedAt: new Date().toISOString(),
reproducible: true,
pageRole: "core",
evidence: 'Observed robots meta tag: content="noindex"',
};
console.log(triage(company, finding));
// { status: "review", priority: "high", ... }
The example uses a reserved demonstration domain; it does not describe a real company.
4. Put AI after evidence collection, not in place of it
An LLM can be useful for translating technical observations into plain-language review notes. The evidence must come first.
A safer instruction might be:
Summarise only the supplied URL, captured evidence, and known context. State uncertainty, describe plausible impact without promising outcomes, and do not invent rankings, traffic losses, revenue figures, or intent to buy.
Keep the raw finding alongside the generated summary so a reviewer can challenge it. If the summary says something that the capture does not support, the raw evidence wins.
5. Measure quality, not just volume
Useful operational metrics include:
- Percentage of candidate websites with strong, independently supported matches
- Percentage of findings that survive manual spot-checks
- Duplicate findings avoided through caching and freshness checks
- Time from detection to human review
- False-positive reasons, grouped by issue type
For outreach, a separate human process should assess relevance, applicable privacy rules, and whether any message would be welcome. Discovery software should not treat an observed error as consent to contact a business.
A practical takeaway
Think of this as an evidence queue, not a cold-email queue. The output is a list of technically plausible situations for an expert to evaluate. That is far more useful than automatically turning every warning into a sales pitch.
Disclosure: I work on ClientFinch, a UK-focused company and website opportunity discovery platform. It surfaces website findings for users to review; users decide whether to contact anyone, and ClientFinch does not send outreach emails. The TypeScript above demonstrates a general design pattern; it is not a copy of ClientFinch's production code.
來源:DEV Community · dev.to