Back to News & Insights
Web Development October 8, 2026 · 7 min read

Our 373 answer pages contain no written answers, because each one runs the rules engine twice at build time

One static page per ingredient and condition, where the verdict, the tier and the reason line are produced by the same engine the app runs on a barcode.

Our 373 answer pages contain no written answers, because each one runs the rules engine twice at build time

People do not search for "gut health app". They search for "is onion low FODMAP", and they want the word no above the fold.

Munchable has a rules engine that answers exactly that question, because that is what the app does when you scan a barcode. So the site carries one static page per ingredient and condition pair the engine has something to say about. There are currently 373 of them, each at the site root, with the question itself as the URL: munchable.app/is-onion-low-fodmap munchable.app/does-coffee-cause-reflux munchable.app/is-cheddar-high-in-lactose munchable.app/answers is the index.

The interesting part is not the page count. It is that nobody wrote the answers.

Every verdict on those pages comes out of the engine. The page does not restate the rules in prose that could drift from them; it builds a one-ingredient product and runs the same fitCheck the app runs, then prints what came back.

It runs twice per page, and the second argument is why. Position on an ingredients list carries information: a legal ingredients list is written heaviest first, so an ingredient at the top is most of the product and the same ingredient near the end is a trace. The engine reads that, which means a single ingredient does not have one answer, it has two. So the page probes it at the front of a list and at the back, and if the two disagree it says so in a sentence instead of picking a side.

That is also where the line on is-olive-oil-ok-with-bam comes from: fat-dense as a main ingredient is an avoid, further down the list a caution. Nobody typed that distinction on that page. It is two function calls and a comparison.

The probe product needs filler so the ingredient under test can sit in the tail of a list rather than being the whole thing. Filler is load-bearing in a way that is easy to miss: anything the rules have an opinion about will contribute reasons of its own, and then the page is answering a question about the padding.

Ours held sunflower oil for a while. It was fine, right up until the bile acid malabsorption rule set learned to read fat-dense ingredients. At that point every single BAM probe came back about the oil, and the page under test had nothing to do with the verdict printed on it. The fix was one line of data and a shouty comment:

The general version of this bug: if you probe a system with a synthetic input, the inert parts of that input are an assumption, and assumptions about a system that is still being developed expire without telling you.

The engine returns one of four verdicts. The page has to turn that into a heading that answers an English question, and the polarity of the question is not constant:

| Condition | The question | A flagged verdict means | | --- | --- | --- | | IBS | Is onion low FODMAP? | No | | Lactose | Is cheddar high in lactose? | Yes | | Reflux | Does coffee cause reflux? | Yes |

Derive the heading from the verdict alone and you print the wrong word on every page where the question is phrased in the direction of the problem rather than the direction of safety. So each condition carries a flagMeansYes boolean next to its phrasing, and one table decides the slug, the h1, the title tag and the FAQ question together, which is the only reason a page's URL and the question it asks cannot drift apart.

The caution string is spelled out per condition rather than shared, because the engine raises that verdict for genuinely different reasons. A moderate lactose load is a question of how much; a reflux trigger is a question of who you are. "It depends on how much" is right for the first and useless for the second, which is why the coffee page opens with "For a lot of people, yes" and then explains that the evidence for trigger foods is low certainty, so the app raises a caution and never an avoid on that axis.

The pages are generated, so their distribution is a fact about the rule sets rather than an editorial choice. Today:

| Condition | Answer pages | | --- | --- | | Bile acid malabsorption | 67 | | IBS and low FODMAP | 64 | | SIBO | 64 | | Lactose intolerance | 52 | | IBD | 48 | | GERD and reflux | 44 | | Gastroparesis | 34 |

Bile acid malabsorption is at the top of that table and used to generate nothing at all. It reads fat, the axis keys on the fat figure printed on the label, and a one-ingredient probe has no nutriment panel, so every BAM probe returned unknown and the generator correctly produced no pages. It only started answering once the rule set gained an ingredient-level view of fat density. A page family appearing as a side effect of a rules change, with no content work at all, is the whole return on this design.

The inverse is also enforced: a pair the rules have nothing to say about gets no page. "Onion is not relevant to bile acid malabsorption" is thin content and nobody searched for it.

These pages live at app/[question]/page.tsx, a dynamic segment at the site root. That is the right URL shape, because the question is the page and a prefix would make the slug worse, but it has sharp edges.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation