A quick audit:
Every already ranks. A few small structural fixes would let it rank on purpose.
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.
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.
- Template fixes
- 1Stop pointing every archive page back to page 1One canonical tag hides most of the archive from Google.
- 2Give the homepage a real title and a headingTitle tag is the single word "Every." No H1.
- Bulk work for an AI agent
- 3Link older essays from new posts and hubs161 strong legacy essays that nothing links to.
- 4Fix the images: size, dimensions, alt text16 MB homepage. 95% of images have no width or height.
- 5Trim the third-party scriptsreCAPTCHA, PostHog, GTM, Facebook on every page.
- 6Write the missing meta descriptions158 legacy posts have none. Cheap and delegable.
- Cheap insurance
- 7Stop claiming every post was updated in 2026The date tracks CMS saves, not edits.
- 8Declare the paywall in the page markupTell Google the gate is intentional.
- Later
- 9Tidy-up tier: 404s, sitemap, redirects, schemaRoutine clean-up an agent can run.
- 10Decide what to do with the 2020 archiveNeeds Search Console clicks first.
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
canonical → page 1
canonical → page 1
canonical → page 1
reachable via sitemap only, no link equity
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
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.
- Title tag:
Every | [tagline] - H1: the tagline or value proposition, written by the Every team, inside the existing design
-
Every other page titled "Every" gets a title that says what the page
is. Tag pages become
[Topic] | Every.
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.
3 · AI agent + DecisionLink older essays from new posts and hubs
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
Here is a CSV of 340 legacy every.to posts with URL, column, title, and word
count. For each post published in the last 90 days on every.to, find up to
three legacy essays over 800 words whose topic genuinely overlaps.
For each match, draft one sentence that could sit in the new post, with the
legacy essay linked on a natural phrase (no "click here"). Output a table:
new post URL, legacy URL, proposed sentence, confidence (high/medium).
Do not edit anything. An editor will review the table.
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
| 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
Two cheap moves, and one that needs a decision:
- Replace reCAPTCHA with Cloudflare Turnstile. Same job, a fraction of the weight, runs on the Cloudflare edge the site already uses, no Google tracking baggage. Drop-in for most form libraries. At minimum, load it only on pages with a form.
- Swap the Vimeo player on legacy articles for a click-to-load thumbnail. The player pulls 742 KB, most of it unused, and it is the reason the legacy article scores 54 on Best Practices. Where the video is gone, remove the embed.
- Decide what tracking you actually use. PostHog session recording alone adds 206 KB and up to 1.5 seconds of main thread. If marketing needs GTM and the Pixel, Cloudflare Zaraz can run those tags at the edge instead of in the browser. This one needs someone from marketing in the room before anything is removed.
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
Six-month trend: the server is getting slower
Weekly 75th-percentile values for every.to, 25 weeks ending September 5, 2026
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
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
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
sees overlay after the intro
sees full text, no explanation
isAccessibleForFree: false +
hasPart pointing at the gated block
same experience
knows the gate is intentional, keeps indexing the full text
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
Body word count by column
Median, with the middle half of posts as a bar
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.
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
- Legacy post with zero clicks, under 500 words, and no external links: noindex, or 301 to a genuine replacement.
- Zero clicks, under 500 words, but it has external links: keep the URL live. Merge the content into a compilation and 301 there.
- Any clicks at all: keep. Add a meta description and two internal links from current posts.
- Author page with fewer than two posts and under 150 words: noindex.
- Digest or roundup older than 18 months with zero clicks: noindex.
- 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
- Origin speed. About 100 to 120 ms at the median for a Next.js app serving personalized pages. Better than most publishers manage with a CDN in front.
- No cloaking. Googlebot and GPTBot receive byte-identical pages. Full article text is server-rendered.
-
AI crawlers. robots.txt allows GPTBot, ChatGPT-User, ClaudeBot,
Claude-User, PerplexityBot, and Google-Extended by name.
llms.txtis spec-compliant. Every article has a Markdown copy at<url>.md, advertised in the Link header. An MCP server card and agent-skills manifest are published under.well-known. These are capabilities, not evidence of traffic, but almost nobody has them. - Basics. Valid sitemap in robots.txt. HTTP to HTTPS, www to apex, trailing-slash redirects all correct. HTTP/3. Security headers present.
- Domain authority. Very high on Ahrefs. That is why the site can carry a few thousand thin URLs without visible damage today, and it is why the fixes above are cheap wins rather than emergencies.
NextWhat to send back
Everything above came from public data. These close the gaps, and they are needed before the archive decisions:
- Search Console Performance report, grouped by page, as a CSV. One to six months depending on the section.
- Search Console Links report, top linked pages.
- An Ahrefs or Semrush export: Best Pages by Links, and Anchors.
-
From the CMS: post count per author handle, and the real
updated_atper post. - 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:
- The original plan labeled the date drift, the soft 404s, and the missing paywall markup "Critical" and cited penalties. Those labels are gone. They are correct fixes with modest evidence of ranking impact.
-
The original plan offered
rel=next/prevas a pagination option. Google stopped using it. Cut. -
The original plan called the missing Article
imagea rich-result failure. Google lists no required Article properties. Moved to the tidy-up tier. - Internal linking from new posts to old essays was not on the original list. It is now item three.
- Pruning was Phase 3 of the plan with target counts. It is now "wait for data," with the rule kept for later.
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
- 500-page crawl from a stratified sample of the sitemap, plus a separate fetch of all 340 legacy posts.
- PageSpeed Insights and the Chrome UX Report via Google's APIs. Lab runs are one synthetic pass under slow-4G throttling and are far harsher than most real users see; field data is the number to trust.
- Internet Archive snapshots for the date check. The fortelabs.com sitemap for the duplicate check, matched on title only.
- No Search Console, Analytics, or backlink data. Orphan counts are "no inlinks found in this crawl," not a census.
- No screenshots. Mobile rendering was assessed from PageSpeed data and markup.