DEV Community

Daniel Pertu
Daniel Pertu

Posted on

Our page about a competitor opens by telling you to go and fix their settings

Notifio has fifteen pages about other companies' alert features and five pages comparing itself to direct rivals. That is thirty-ish screens of writing about products I do not own, published on a site that wants you to buy mine.

The lazy version of this page writes itself: their alerts are slow, ours are fast, here is a button. We do not write that, and the reason is not politeness. It is that the lazy claim is the one a reader can disprove in ten seconds, and a page that loses an argument in its first paragraph does not get a chance to make the true one.

So our page about Rightmove alerts opens by telling you to go and change Rightmove's settings:

Credit where it is due: Rightmove lets you set Property Alerts to instantly, daily, every 3 days or every 7 days, and if you have them on daily you should change that today. The instant setting is the right one and it costs nothing.

That is on a page whose job is to sell a competing product. Here is why it is there, and the two type definitions that keep it there.

The claim we are not allowed to make

The doctrine is written at the top of the file holding the UK portal pages, and the last sentence of it is the rule:

That distinction is what these four pages are about, and it is why none of them claims the portals are slow.

"Slow" is a bad claim for us for three separate reasons. It is unfalsifiable in the direction that matters, since any individual reader's last alert probably did arrive quickly. It ages badly, because a company can fix a latency problem in a sprint and then your page is wrong. And it is a claim about someone else's infrastructure made by someone with no access to it.

The claim that survives all three tests is structural. A portal alert is an announcement about an event, and the events are specific: a property being published, or a price being reduced by 2% or more. It is not a view of your search. So anything that changes a property's eligibility after publication reaches you only if the portal decides to re-announce it. A let falls through. An agent takes a property down and puts it back. A price drops by one percent and lands inside your filter.

That claim is checkable, it is about design rather than performance, and it does not get fixed by a faster mail server. It is also, usefully, the actual product difference: comparing a search results page against its previous state every thirty seconds has no concept of "newsworthy", so it catches all of those.

What the page does say about timing is a count of the things between the decision and the inbox, which is the subject of Count the hops: why "instant" notifications are never instant. "Instant" names the moment the portal decides you should hear about a property. It does not name the moment you can read it, and the gap contains a matcher working out whose saved searches match, a bulk mailer sending the same announcement to everybody on that list, and a send queue emptying at its own pace. None of that is a criticism of the portal. Every alert eventually arriving is a perfectly good target. It is just a different target from your alert arriving first.

The type will not let a claim go out naked

Every one of those pages is a typed object, and the section that describes a third party's behaviour cannot be constructed without its receipts:

/**
 * A claim about a third party's product, with the page it came from.
 *
 * Anything we say about how another company's alerts behave carries one of
 * these. The citation is rendered on the page, which keeps us honest and gives
 * a reader a way to check a detail that may have changed since we wrote it.
 */
export type Source = {
  label: string;
  url: string;
};

nativeAlerts: {
  heading: string;
  body: string[];
  sources: Source[];   // not optional
};
Enter fullscreen mode Exit fullscreen mode

sources: Source[] with no ?. You cannot add a page that describes a competitor's alerts without going and finding the help-centre article that says so. In practice that constraint did most of the editing: a few things I believed about these products turned out to have no citable basis, and they are not on the pages.

And the citation is not a footnote nobody renders. The template prints it with a lead-in that is close to an invitation to catch us out:

{site.nativeAlerts.sources.length > 0 && (
  <p className="mt-5 text-[12px] ...">
    Sources, so you can check whether {site.name} has changed this since we wrote it:{" "}
    {site.nativeAlerts.sources.map((source, i) => (
      <span key={source.url}>
        {i > 0 && " · "}
        <a href={source.url} rel="nofollow noopener" target="_blank" ...>
          {source.label}
        </a>
      </span>
    ))}
  </p>
)}
Enter fullscreen mode Exit fullscreen mode

"so you can check whether X has changed this since we wrote it" is doing two jobs. For the reader it is a statement that the claim has a shelf life and we know it. For me it is a standing admission that these pages are a maintenance liability, written into the page rather than into a ticket I would not read.

The comparison table is allowed to say they win

The head-to-head pages have a second type, and one optional field on it carries most of the credibility:

export type CompareRow = {
  dimension: string;
  them: string;
  us: string;
  /** Set when the honest answer favours them, so the table can show it. */
  advantage?: "them" | "us" | "even";
};
Enter fullscreen mode Exit fullscreen mode

Three possible values, and the only one that earns anything is "them". A comparison table with a tick in every row on one side is not read as information. It is read as an advertisement, by a reader who then stops believing the rows they could not check either.

And whereTheyWin is a required field with a stated rule attached:

/**
 * Second, `whereTheyWin` is not optional and is not a straw man. Notifio runs on
 * the user's own machine, which is a genuine disadvantage against a cloud
 * service if that machine is a laptop that gets closed at night. A comparison
 * page that hides that loses the reader the first time they think about it, and
 * pages that concede a real point outrank pages that do not.
 */
Enter fullscreen mode Exit fullscreen mode

Here is what that produces in practice, from the Rentbird comparison:

It runs in the cloud, and that is not a small thing. Rentbird's bots do not care whether your laptop is shut, out of battery, or in a bag on a train. Notifio only monitors while the machine it is installed on is awake and online, so if you do not have a desktop or a laptop you are willing to leave running, Rentbird will catch listings that Notifio sleeps through. That is the single most important difference between the two products and it is the one we lose.

That paragraph is the first thing in that section, not a hedge buried at the bottom. It is also true, and it is the first thing a thoughtful reader is going to work out on their own about a desktop app. Getting there first is the only way that thought lands as "they are being straight with me" rather than "they did not mention this".

The counting argument on the next section is then allowed to be blunt, because it has earned it: a monthly subscription against one payment of £20, which is less than their first month.

The prices are the part that rots

Every price on a comparison page is quoted from the other company's own pricing page. From the same type header:

First, every price is quoted from the other company's own pricing page and carries a dated source link on the page. Subscription prices change; a comparison page that cannot be checked is a liability rather than an asset.

Writing this post is how I found out that the word "dated" in that comment is aspirational. CompareSource is { label: string; url: string }. There is no date field, and the template renders the label and the href and nothing else. So the doctrine says the links carry a date and the pages do not, which on a post about required fields beating good intentions is an unusually on-the-nose thing to discover. Adding checkedOn to both source types and rendering it is now the next change to these files.

The accounting that is honest: this approach turns five marketing pages into five things with an expiry date on them. The type is what makes the expiry tractable even without the date. Every claim about a third party sits in either a sources array or a plans array, both of which are greppable, so refreshing the pages is a list rather than a reading exercise.

I have been wrong about these pages before. I audited the generated ones once and found most of what they asserted was unsourced guessing, which I wrote up in We audited our own programmatic SEO pages and most of them were guesses. The required sources field is the structural fix for that, in the same way that the per-site prose requirement in Fifteen nearly identical pages, and a type that refuses to let them be identical was the structural fix for templated filler, and the function described in The four line function that stops our comparison page cheating was the fix for a table that could quietly stop conceding.

If you write pages about competitors

Four things I would keep:

  1. Make the strongest version of their case a required field. Optional honesty is decorative. A type error is not.
  2. Attach the receipt and render it. An uncited claim about another company is a claim you will not be able to defend in two years when you have forgotten where it came from.
  3. Prefer structural claims to performance claims. "They are slow" expires and is rude. "Their alert describes an event rather than your search" is checkable, stays true, and is the thing you actually do differently.
  4. Let the table say they win a row. The concession is what makes the other rows worth reading.

You can read the output on the alerts index and the comparison index, and the market reality all of it rests on is in how fast do rental listings go.

Top comments (0)