"Can my dog eat X?" is one of the most searched pet questions on the internet, and most answers bury the verdict under 1,500 words of filler. When I built Animals Details, a set of pet-owner guides covering dogs, cats, horses, food, health and other pets, I wanted the opposite: the answer first, then the nuance.
This post is about the content model behind that — which turned out to be more of a data-modeling problem than a writing problem.
The verdict is data, not prose
Every food-safety guide answers the same question, so it gets the same shape. Instead of free-form articles, each guide starts from a typed record:
type Verdict = "safe" | "caution" | "toxic";
interface FoodGuide {
species: "dog" | "cat" | "horse" | "other";
food: string; // "peanut butter"
verdict: Verdict;
headline: string; // "Plain is OK; check for xylitol"
watchFor: string[]; // ingredients or conditions that flip the verdict
servingNote?: string; // "occasional treat, small amount"
callVetIf: string[]; // symptoms that mean "stop reading, call a vet"
updated: string; // ISO date
}
Peanut butter is a good example of why caution exists. Plain peanut butter can be an occasional treat — but some brands contain xylitol, which is dangerous for dogs, and many add a lot of sugar and salt. A binary safe/unsafe flag would be wrong either way. The watchFor list is what carries that nuance, and it renders at the very top of the page.
Render the answer above the fold
Because the verdict is structured, the page template can put it where readers need it:
function VerdictBanner({ guide }: { guide: FoodGuide }) {
const label = { safe: "Generally safe", caution: "Use caution", toxic: "Do not feed" }[guide.verdict];
return (
<aside className={`verdict verdict--${guide.verdict}`}>
<strong>{label}</strong>
<p>{guide.headline}</p>
{guide.watchFor.length > 0 && (
<p>Watch for: {guide.watchFor.join(", ")}</p>
)}
</aside>
);
}
Color alone never carries the meaning — the label text is always there for accessibility and for people who print pages.
Structured data from the same record
The same record generates FAQ structured data, so what search engines see always matches what's on the page:
function faqJsonLd(g: FoodGuide) {
return {
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: [{
"@type": "Question",
name: `Can ${g.species}s eat ${g.food}?`,
acceptedAnswer: { "@type": "Answer", text: g.headline },
}],
};
}
One source of truth means no drift between the visible answer and the markup.
Topics as a simple taxonomy
Guides are grouped into a small, flat set of topics — Dogs, Cats, Food, Health, Horses, Other pets — with a filterable "live guides" list on the homepage. A flat taxonomy was a deliberate choice: deep category trees look nice in a CMS but make internal linking and navigation worse for a site this focused.
Safety content needs guardrails
Pet health content can do real harm if it's wrong or overconfident, so a few rules are baked into the template:
- Every guide has a "call your vet if…" block that can't be left empty.
- Toxic verdicts link to emergency guidance rather than serving suggestions.
- Every guide shows an updated date, and older guides get flagged for review.
Takeaways
- If every page answers the same question, model the answer as data.
- Include a middle "caution" state — real-world answers are rarely binary.
- Generate structured data from the same record as the visible content.
- For health topics, make safety caveats a required field, not an afterthought.
The guides are at animalsdetails.com. If you've built content sites around structured records like this, I'd like to hear how you handled editorial review.
Top comments (0)