September 2026

SEO AUDIT

Presented by Nick Gray and his Claude Code agents


A quick audit:

Every already ranks. A few small structural fixes would let it rank on purpose.

every.to, crawled September 9, 2026 500 pages sampled, all 340 legacy posts fetched, 2,444 sitemap URLs checked Public data only. No Search Console access.
Nick Gray

Nick Gray

Nick runs SEO for a small group of publishers. His agents crawled the site and wrote the first draft. Nick reviewed it twice and cut what did not hold up.

Every is a media company that has never worked on SEO, and it shows in a good way. The site is server-rendered, the origin answers in about 100 ms, every article has a Markdown mirror, and every major AI crawler is welcomed by name. Most publishers have none of that. What is missing is a handful of template-level fixes that stop Google from seeing the archive, and a few bulk clean-up jobs that an AI coding agent can do end to end.

A note from NickThe spirit of everything below is small structural changes to fix the low-hanging fruit. Nothing here is aggressive, and nothing verges on spammy. Anything that would need a big content or design effort is out of scope. You can do most of this yourselves. Where a task suits a coding agent, there is a prompt you can paste.

Start hereThe 20% that does most of the work

Ranked by expected impact for a high-authority editorial site, with cost in mind. Click any card to jump to the details.

  1. Template fixes
  2. 1Stop pointing every archive page back to page 1One canonical tag hides most of the archive from Google.Engineer
  3. 2Give the homepage a real title and a headingTitle tag is the single word "Every." No H1.EngineerDecision
  4. Bulk work for an AI agent
  5. 3Link older essays from new posts and hubs161 strong legacy essays that nothing links to.AI agentDecision
  6. 4Fix the images: size, dimensions, alt text16 MB homepage. 95% of images have no width or height.AI agent
  7. 5Trim the third-party scriptsreCAPTCHA, PostHog, GTM, Facebook on every page.EngineerDecision
  8. 6Write the missing meta descriptions158 legacy posts have none. Cheap and delegable.AI agent
  9. Cheap insurance
  10. 7Stop claiming every post was updated in 2026The date tracks CMS saves, not edits.Engineer
  11. 8Declare the paywall in the page markupTell Google the gate is intentional.EngineerDecision
  12. Later
  13. 9Tidy-up tier: 404s, sitemap, redirects, schemaRoutine clean-up an agent can run.EngineerAI agent
  14. 10Decide what to do with the 2020 archiveNeeds Search Console clicks first.Wait for dataDecision
AI agent bulk work Engineer template or config change Decision a human call first Wait for data needs Search Console

1 · EngineerPagination points every archive page back to page 1

every.to/napkin-math?page=2 and every deeper page carry a canonical tag to /napkin-math. That tag tells Google "this page is a duplicate of page 1, ignore it." So Google ignores it, and it ignores the links on it. The posts listed on pages 2 through 50 have no crawl path except the sitemap.

In the crawl, 77 of 90 legacy posts and 45 of 185 recent posts had no internal link pointing at them from any crawled page. That is a sample, not a census, but the mechanism explains it.

How the canonical tags orphan the archive

What Googlebot can and cannot follow today

Homepage
↓
Column hub, page 1 (canonical: itself)
↓
page 2
canonical → page 1
page 3
canonical → page 1
page 4 … 50
canonical → page 1
✕ Google does not follow links from pages it treats as duplicates
Posts listed only on pages 2 and up
reachable via sitemap only, no link equity
The fix: give each paginated page a self-referencing canonical and keep ordinary links to the next page. Google no longer reads rel=next/prev, so do not bother with it.

Posts with no internal links found in the crawl

Share of sampled posts with zero inlinks from any crawled page

Legacy posts: 77 of 90 sampled. Recent posts: 45 of 185 sampled. Source: 500-page crawl, September 9, 2026.
For the engineer

On every /column?page=N route, set the canonical to the page's own URL (including the page parameter). Keep the "next" and "previous" links as real anchors with href, not JavaScript-only buttons. If the archive uses a "load more" button, give each load a crawlable URL.

Verify:

curl -sA "Googlebot" https://every.to/napkin-math?page=2 | grep -o '<link rel="canonical"[^>]*>'
# expect: href="https://every.to/napkin-math?page=2"

Then re-crawl and confirm the orphan count falls. Do this before any homepage change. Today the homepage's list of recent essays is the only internal path to a large share of the archive.

2 · Engineer + DecisionThe homepage title is the single word "Every"

Five characters. Search engines show the title tag as the blue link in results. Right now the homepage, /subscribe, /newsletter, /columnists, /studio, /sitemap, and all 57 tag pages under /c/ share the same one. The homepage also has no H1, and four identical H2s.

Every already has a tagline in its own meta description and llms.txt: "The only subscription you need to stay at the edge of AI." Use that, or whatever the team prefers. The audit will not invent brand copy.

There is no rule that a title must be under 60 characters or that a page must have exactly one H1. The point is that the title and the main heading should say something.

Agent prompt: give every "Every"-titled page a real title
Crawl every.to and list every page whose <title> is exactly "Every".
For each, propose a title in the form "[Page purpose] | Every" using the page's
own H1 or first heading, under about 65 characters. Do not invent claims.
Then find the Next.js layout or page component that sets <title> for those
routes (/subscribe, /newsletter, /columnists, /studio, /sitemap, /c/[tag]) and
wire in the per-page title. Leave the homepage title for the team to write.
Verify by re-crawling: the count of pages titled "Every" should be zero.

The legacy archive holds 161 substantial essays, most over 1,000 words, on topics Every still writes about. Almost nothing links to them. Fixing pagination makes them crawlable. Contextual links from current posts make them matter. It also puts good older work back in front of readers.

This is a proposal-and-approval task. An agent reads each new post, finds two or three relevant older essays, and drafts the sentence and anchor. An editor accepts or rejects. Nobody should let an agent insert links unreviewed.

Agent prompt: propose internal links to the legacy archive

The CSV is at the bottom of this page.

4 · AI agentImages: size, dimensions, alt text

Three separate problems, one component to fix. The homepage ships 16 MB of images on desktop across 89 requests, and 11.5 MB on mobile. A recent article ships 8.7 MB across 20 images, about 440 KB each. The site references next/image 469 times, but the delivered sizes are not trimmed to the viewport. Images are the single largest contributor to page weight on the site.

Image bytes per page

Transferred image bytes in a single PageSpeed run, September 9, 2026

A reasonable target for the homepage is under 2 MB. Source: PageSpeed Insights lab run.
Finding Evidence Why it matters
Oversized images 16 MB homepage desktop, 8.7 MB recent article Slow loads, especially on phones. Largest byte cost on the site.
Missing width and height 10,684 of 11,274 post images. 111 of 138 homepage images. The browser cannot reserve space, so the page jumps as images load. Mobile homepage layout shift is already over Google's threshold.
Missing alt text 27% of post images, 60% of homepage images, 65% of author-page images Accessibility first. Also image search. Not a big ranking lever on its own.
Google Discover Not checked in this audit Discover wants a crawlable image at least 1,200 px wide and max-image-preview:large in robots meta. Worth confirming while in the image component.

Every runs on Cloudflare already. Cloudflare Images can resize and convert to WebP or AVIF at the edge with a URL prefix, without touching the CMS. If the team prefers to stay inside Next.js, the fix is the sizes prop and a sane deviceSizes list.

Agent prompt: dimensions and alt text on every image
In this Next.js codebase, find every component that renders article images,
homepage images, and author images.
1. Every image gets intrinsic width and height (from the CMS record or by
   reading the file). Do not lazy-load the first image above the fold; lazy-
   load everything below it.
2. Every next/image gets a sizes prop that matches the column it renders in.
   Check the delivered image width in the network panel at 390px and 1440px
   viewports; it should be within 2x of the rendered size.
3. Alt text: for each image without alt, look at the image itself and its
   caption. Decorative images get alt="". Charts and screenshots get a one-
   sentence description of what they show. Do not guess from surrounding
   prose alone. Follow https://www.w3.org/WAI/tutorials/images/decision-tree/
4. Confirm the article template's og:image is at least 1200px wide and that
   robots meta includes max-image-preview:large.
Verify with a crawl: count of images missing width/height and missing alt
should both fall to near zero. Report before/after homepage image bytes.

5 · Engineer + DecisionThird-party scripts load on every page

Google reCAPTCHA, PostHog with session recording, Google Tag Manager, gtag, and the Facebook Pixel load on every page tested, including a 2020 article with no form and the subscribe page. Together that is about 1.6 MB of third-party weight before any Every content loads. On the subscribe page, the one page where people convert, they block the main thread for 5.2 seconds on mobile in the lab. The page content itself is light.

Main-thread time by third party on /subscribe

Milliseconds of blocking work, mobile lab run

Total Blocking Time on this page was 5,190 ms, the worst of any page tested. Source: PageSpeed Insights, September 9, 2026.

Two cheap moves, and one that needs a decision:

What real users see

Field data is what Chrome users experienced over the last 28 days. The homepage passes on the big three. Layout shift on mobile is over the line, and server response time on the subscribe page is slow.

Core Web Vitals, field data, 75th percentile

Chrome UX Report, collection period August 11 to September 7, 2026

Page
LCP
INP
CLS
TTFB
Homepage, mobile
2,404 msgood
169 msgood
0.11needs work
753 msgood
Homepage, desktop
1,974 msgood
96 msgood
0.07good
626 msgood
All pages, mobile
3,011 msneeds work
135 msgood
0.08good
859 msneeds work
All pages, desktop
2,364 msgood
69 msgood
0.10on the line
656 msgood
/subscribe, mobile
2,191 msgood
n/alow traffic
0good
1,060 msneeds work
LCP is when the main content appears. INP is how fast the page responds to a tap. CLS is how much the layout jumps. TTFB is how fast the server answers. Green passes Google's threshold, amber needs improvement.

Six-month trend: the server is getting slower

Weekly 75th-percentile values for every.to, 25 weeks ending September 5, 2026

Time to first byte is up 57% on phones and 66% on desktop. Interaction delay is up 19% and 25%. Paint timing improved over the same period, so the front end got better while the back end got slower. Nothing is in the poor band yet. The homepage A/B middleware adds a server hop on this path and is worth checking for synchronous lookups.
For the engineer

Every HTML response is cache-control: private, no-cache and cf-cache-status: DYNAMIC. Every crawler request hits the origin. If anonymous responses can be separated from personalized ones, a public s-maxage at the Cloudflare edge for logged-out visitors would remove most of the TTFB problem at once. Test subscriber, anonymous, and metered states before turning it on. A cache hit is not proof it is safe.

Also on the list: two Next.js CSS chunks are 90 to 97 percent unused and block rendering, and the homepage runs 5.3 seconds of first-party JavaScript across about 90 script tags. That is a hydration and code-splitting review, not a quick fix. Two posts under /working-overtime/ answer in 3 to 5 seconds against a site median of 100 ms; that template has a slow upstream call.

6 · AI agent158 legacy posts have no meta description

The meta description is the grey text under the blue link in search results. Google often writes its own, but an accurate one improves how the listing reads and sometimes the click rate. It is not a ranking factor. It is on this list because it is cheap, fully delegable, and easy to verify.

Meta description coverage by column, legacy posts

340 posts with numeric slug suffixes, fetched September 9, 2026

Forte Labs is 74 of 79 missing. Talk Therapy is 36 of 36. About 17 of 185 sampled recent posts are also missing one.
Agent prompt: write the missing meta descriptions
Here is a CSV of every.to posts. For each row where
meta_description_present is "no", read the full article at the URL and write
a meta description of 120 to 155 characters that summarizes what the article
argues or teaches. Summarize the whole piece, not just the opening paragraph.
Plain language, no clickbait, no quotes around it. Never overwrite a
description that already exists. Output: url, proposed_description.
Give me the first 20 to review before doing the rest.

Have an editor read a sample before the batch is published. Then the engineer wires the field into the Article template and the description property of the JSON-LD.

7 · EngineerEvery post says it was updated in 2026

Every legacy post shows an "Updated" label with a 2026 date. The Article schema carries a matching dateModified. The sitemap lastmod carries the same value. The agent checked one post against the Internet Archive.

One post, four snapshots, same body text

How To Work Effectively With A Personal Assistant, published May 2020

Snapshots: August 2024, May 2026, and the live page. The article body is 497 words in all three. Only the template chrome changed.

Across the site, 340 of 340 legacy posts and 172 of 185 sampled recent posts carry a 2026 dateModified. In the sitemap, 961 of the first 1,000 post entries have a 2026 lastmod. The field tracks CMS saves and template changes, not edits.

Why it matters: a reader should not mistake a 2020 essay for something reviewed this year. Google reads the date as a freshness claim, and when its own guidance says the claim does not match a change, it stops trusting the sitemap's lastmod as a recrawl signal. That is a lost signal, not a penalty.

For the engineer

Stop writing dateModified and the "Updated" label from the CMS updated_at column. Write it only on a real editorial edit. For the backfill, where there is no edit history, set dateModified equal to datePublished or omit it. Do not invent a date. Make the sitemap lastmod read the corrected field.

Verify: spot-check ten legacy posts against Internet Archive snapshots. The date should match the last real edit or the publish date.

8 · Engineer + DecisionDeclare the paywall in the page markup

On every post, the full article text is in the server HTML. The "Unlock this article" box is a client-side overlay. Googlebot sees the complete article, and the Every team wants it to stay that way. Good. But no page tells Google that the gate is intentional. Google has a documented mechanism for exactly this: isAccessibleForFree plus a hasPart element that names the gated block.

What each visitor gets

Today, and with the markup added

Today
Server HTML: full article text
↓
Reader
sees overlay after the intro
Googlebot
sees full text, no explanation
With markup
Server HTML: full text + isAccessibleForFree: false + hasPart pointing at the gated block
↓
Reader
same experience
Googlebot
knows the gate is intentional, keeps indexing the full text
The markup is a declaration of reader access. It does not block the crawler and it does not enforce anything. It protects the current setup and any future change to the wall.
A note from NickThis section is worth investigating more. I do not want to rely only on this audit for paywall advice. Talk to someone with direct experience running a metered or gated publisher through Google's paywalled-content program before making changes here. And decide the wall architecture in writing first: overlay over full server text (today), or server-side truncation (Google would then index only the preview unless the crawler is served the full text with this markup).
For the engineer: copy-ready JSON-LD

Add to the Article schema on every walled post. Replace .paywall-gate with the real, stable selector that wraps the gated block. If the first paragraphs stay free, add a second WebPageElement with isAccessibleForFree: true and the selector of the free block.

{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "…",
  "description": "…",
  "image": "https://…/social_media_image.jpg",
  "datePublished": "2026-07-10T11:37:39-04:00",
  "dateModified": "2026-07-10T11:37:39-04:00",
  "author": { "@type": "Person", "name": "Dan Shipper", "url": "https://every.to/@danshipper" },
  "publisher": {
    "@type": "Organization", "name": "Every", "url": "https://every.to",
    "logo": { "@type": "ImageObject", "url": "https://every.to/android-chrome-512x512.png", "width": 512, "height": 512 }
  },
  "mainEntityOfPage": { "@type": "WebPage", "@id": "https://every.to/chain-of-thought/…" },
  "isAccessibleForFree": false,
  "hasPart": [
    { "@type": "WebPageElement", "isAccessibleForFree": false, "cssSelector": ".paywall-gate" }
  ]
}

Google's current documentation uses the boolean false. Older examples used the string "False"; both are accepted, but use the boolean and keep it consistent between the Article and the element. Verify with the Rich Results Test on one walled article. Full reference: Google's paywalled content guide.

Related decision: the .md article mirrors, llms-full.txt, and the MCP server all serve full text with no wall. That is a product choice, and it should be a deliberate one.

9 · Engineer + AI agentThe tidy-up tier

None of these move rankings on their own. They are the kind of thing a crawler flags forever until someone clears them. Most suit an agent with a verification step.

Fix Today Verify with
Return a real 404 for missing pages Any unknown URL under /, /column/, /@handle, /p/ returns 200 with a noindex tag. Google copes, but it is wrong HTTP and it wastes crawl. curl -I https://every.to/does-not-exist returns 404
Sitemap clean-up Lists /subscribe, which 307s to a noindexed page. Two URLs contain raw spaces (/@Every Staff). Every post says changefreq: weekly. 52 entries have no lastmod. About 200 live URLs are missing: podcast episodes, /guides/, /news, /events. xmllint plus a diff
Redirect the old column slugs /napkinmath/, /talktherapy/, /meansofcreation/, /askjerry/, /praxis/ (29 URLs), /news/ all return 200 with a canonical to the hyphenated version. Make them 301s. curl -I on one URL per slug returns 301
Mixed-case paths /Napkin-Math returns 200 and canonicalizes to itself. Redirect known column aliases to lowercase. Do not blanket-lowercase every route; some identifiers may be case-sensitive. curl -I https://every.to/Napkin-Math returns 301
Remove the leaked admin link /admin/posts/master-the-art-of-storytelling-… appears as a live link in public HTML. Grep rendered HTML
Article schema on legacy posts image is missing and description is empty. Neither is required by Google, but both are recommended and both are easy: fall back to og:image and the new meta description. Rich Results Test on one legacy post
Breadcrumbs and author entities No BreadcrumbList anywhere. Author is a bare string on most posts. Link it to the author page with a Person entity. Rich Results Test
Bing and IndexNow No Bing Webmaster Tools coverage was visible, and no IndexNow key. Bing feeds several AI answer engines. Set up Bing Webmaster Tools and add IndexNow submission from the publish pipeline. Bing Webmaster Tools shows submissions
robots.txt Add an explicit User-agent: OAI-SearchBot allow line. The wildcard covers it today. robots.txt

10 · Wait for dataThe 2020 archive

340 sitemap URLs are Substack-era posts with numeric slug suffixes like -537989. The agent fetched all of them, not a sample. Here is what they are.

Legacy posts by type

340 posts, classified by body content

90 podcast pages have a median of 174 words. 161 are real essays, comparable in length to current posts.

Body word count by column

Median, with the middle half of posts as a bar

Forte Labs median is 2,134 words. Talk Therapy median is 72. Both are "legacy," and they need opposite treatment.

Crawl budget is not the problem; the site is small enough that Google crawls all of it. The question is whether 133 posts under 500 words, 187 author pages with a median of 99 words, and 57 tag pages titled "Every" make the site look thinner than it is. Google has no word-count rule, and a short podcast page can serve its purpose. But zero-click stubs from 2020 are not serving anyone.

The Forte Labs question

79 legacy posts sit under /forte-labs/. The agent matched their titles against the 559 posts in the fortelabs.com sitemap. 56 have a matching post on Tiago Forte's own site, and both copies declare themselves canonical. Two examples: this one on every.to and the same title on fortelabs.com.

Forte Labs posts on both domains

Matched by title against the fortelabs.com sitemap. Body text was not compared.

56 of 79 every.to Forte Labs posts have a title match on fortelabs.com. The full match list is in the CSV at the bottom of this page.

Google will pick one copy to rank. The question for Every is which one it wants that to be. If Forte Labs is the original publisher and Every does not care about ranking these, a cross-domain canonical from the every.to copy to the fortelabs.com copy settles it. If Every wants to keep the ranking, the reverse needs Tiago's team. Either way, confirm the bodies match before touching canonicals, and make the choice a publishing decision, not a technical default.

The pruning rule, for when the data exists

A note from NickI would apply this with Search Console data over a one- to six-month window, depending on how much traffic a given section gets. Longer windows for the quiet corners of the archive, shorter for the busy ones. Do not apply it before the pagination fix, because a post with zero clicks today may simply be a post Google could not reach.
  1. Legacy post with zero clicks, under 500 words, and no external links: noindex, or 301 to a genuine replacement.
  2. Zero clicks, under 500 words, but it has external links: keep the URL live. Merge the content into a compilation and 301 there.
  3. Any clicks at all: keep. Add a meta description and two internal links from current posts.
  4. Author page with fewer than two posts and under 150 words: noindex.
  5. Digest or roundup older than 18 months with zero clicks: noindex.
  6. Never delete a URL that has external links. Keep it live as a redirect.

/thesis-statements/ pages are short by design and an editorial priority. No change recommended there.

All 340 legacy posts (sortable, filterable)

Download the CSV. Columns: URL, column, title, published, body words, meta description present, type, recommendation, duplicate on fortelabs.com.

URL Column Published Words Meta Type Recommendation

Already done wellWhat Every should not touch

NextWhat to send back

Everything above came from public data. These close the gaps, and they are needed before the archive decisions:

  1. Search Console Performance report, grouped by page, as a CSV. One to six months depending on the section.
  2. Search Console Links report, top linked pages.
  3. An Ahrefs or Semrush export: Best Pages by Links, and Anchors.
  4. From the CMS: post count per author handle, and the real updated_at per post.
  5. The wall architecture decision, in writing.

With those, the pruning list can be finalized down to the URL, and the internal-link proposals can be prioritized by the pages that already get impressions.

CheckedA second opinion

Before this page was written, the full audit and action plan were sent to OpenAI's gpt-6-astra model (medium reasoning effort) with the instruction to push back hard. It agreed on the top items: archive crawl paths, titles, image delivery, and third-party scripts. It disagreed in places, and this page changed as a result:

Where the two disagreed and Nick sided with the original: meta descriptions stay in the top six because they are cheap and delegable, and the Forte Labs duplicates stay on the page as an open question rather than being left out.

Methods and limitsHow this was made

Prepared as a favor for a friend at Every. Not indexed. The Every wordmark and title lettering belong to Every and are used here only to make this page feel at home.