Why Squarespace Quietly Stops Indexing Your Blog Posts (And How to Fix It)

SEO
Squarespace SEO Sucks

Open Google Search Console for almost any Squarespace site that has been publishing for a few years, go to the Pages report, and scroll down to the Not indexed section. There is a good chance you will see what we saw on our own site: a quiet stack of perfectly good blog posts sitting under "Crawled - currently not indexed" and "Discovered - currently not indexed," missing from search and slowly bleeding away the authority you spent years building.

The instinct is to blame the writing. Rewrite the thin posts, add more words, sprinkle in keywords, request indexing, and wait. On Squarespace that instinct is usually wrong. When posts drop out of the index here, the cause is almost never content quality. It is structure. More precisely, it is the way Squarespace links its own blog together, which strands older posts too far from any page Google crawls often. Fix the structure and the posts come back.

We hit this on search.agency, traced it to its root, and applied a set of fixes that any Squarespace site can copy. Below is the full diagnosis and the exact code we used, with the parts you can paste straight into your own site.

First, read what Search Console is actually telling you

Two statuses do most of the damage on Squarespace, and they do not mean the same thing. Reading them correctly tells you whether you have a crawl problem or a quality problem.

Search Console status What Google is telling you The usual Squarespace cause
Discovered - currently not indexed Google knows the URL exists but has not crawled it yet. A crawl priority problem. The post is buried so deep in your link structure that Google deprioritized fetching it at all.
Crawled - currently not indexed Google fetched the page, looked at it, and chose not to index it. A value judgment. The post looks too isolated, too similar to others, or too thin to earn a slot. Isolation is the most common and the most fixable.

Both statuses point back to the same root on a Squarespace blog: deep posts that almost nothing links to. Solve the linking and you address both buckets at once.

Why Squarespace does this to you

Three defaults in how Squarespace builds a blog work against your back catalog. Individually they look harmless. Together they bury everything you published more than a few months ago.

The first is that blog pagination is an offset chain. Your `/blog` index shows the newest handful of posts. Everything older lives on `/blog?offset=...` pages, each reachable only by clicking "older" again and again. Page six of your blog is six hops from any page Google visits regularly. Googlebot follows links and budgets its time, so a post sitting eight clicks deep, linked only from a pagination page Google rarely loads, gets crawled rarely and then quietly dropped. That is the engine behind "Discovered - currently not indexed."

The second is that posts do not link to each other. Squarespace has no true "related posts by category" block out of the box. So a strong essay from 2022 links out to nothing and is linked to by nothing except the offset page it once sat on. It is, in practical terms, an orphan. Orphans are the first pages to fall out of the index and the last to come back.

The third is that category pages render from the same template as `/blog`. Your `/blog/category/SEO` page uses the identical hero, the same title pattern, and the same layout as the main blog. To Google it can read as a near duplicate of `/blog` rather than a distinct topical hub, so it gets canonicalized away or ignored. You lose the one structure that would otherwise funnel crawl equity into tight clusters of related posts.

Notice that none of these is about the words on the page. They are all about how pages connect. That is why the fix is structural.

The fix, in five moves

The principle behind all five: give every post more than one short path from a page Google already crawls often, and make your category pages look like real hubs instead of copies of the blog index. Two changes do most of the heavy lifting, and three smaller ones close the gaps.

Move 1: Publish a flat archive page

This is the single highest leverage change. An archive page is one URL that links directly to every post you have ever published. Once Google crawls it, every post is exactly one click deep instead of six. The deep pagination chain stops mattering.

In Squarespace, create a new page (Pages, then the plus under Not Linked, then Blank Page), name it Blog Archive, and set its URL slug to `archive` so it lives at `yoursite.com/archive`. Open the page, add a Code Block, set it to HTML, and paste in the markup below. It is fully scoped to a `.sa-archive` class so the styling will not leak into the rest of your site.

<style>
  .sa-archive { max-width: 820px; margin: 0 auto; padding: 1rem 0; font-family: inherit; line-height: 1.55; }
  .sa-archive-intro { color: #555; margin-bottom: 1.75rem; }
  .sa-archive-toc { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 2.5rem; }
  .sa-archive-toc a { display: inline-block; padding: 0.35rem 0.75rem; border: 1px solid #ddd; border-radius: 999px; font-size: 0.9rem; text-decoration: none; color: #222; }
  .sa-archive-toc a:hover { background: #111; color: #fff; border-color: #111; }
  .sa-archive h2 { margin-top: 2.25rem; margin-bottom: 0.75rem; font-size: 1.5rem; border-bottom: 1px solid #eee; padding-bottom: 0.4rem; }
  .sa-archive-list { list-style: none; padding-left: 0; margin: 0; }
  .sa-archive-list li { padding: 0.55rem 0; border-bottom: 1px solid #f3f3f3; display: flex; gap: 0.75rem; align-items: baseline; flex-wrap: wrap; }
  .sa-archive-date { color: #888; font-variant-numeric: tabular-nums; min-width: 70px; font-size: 0.9rem; }
  .sa-archive-list a { color: #111; text-decoration: none; font-weight: 500; flex: 1; }
  .sa-archive-list a:hover { text-decoration: underline; }
  .sa-archive-cats { color: #888; font-size: 0.8rem; }
  @media (max-width: 600px) { .sa-archive-date { min-width: 60px; font-size: 0.8rem; } }
</style>

<div class="sa-archive">

  <p class="sa-archive-intro">Every post we have published, grouped by year. The fastest way to find older work without paging through the blog.</p>

  <nav class="sa-archive-toc" aria-label="Jump to year">
    <a href="#y-2026">2026</a>
    <a href="#y-2025">2025</a>
    <a href="#y-2024">2024</a>
  </nav>

  <h2 id="y-2026">2026</h2>
  <ul class="sa-archive-list">
    <li><span class="sa-archive-date">May 19</span> <a href="/blog/your-post-slug">Your post title goes here</a> <span class="sa-archive-cats">SEO</span></li>
    <li><span class="sa-archive-date">Apr 02</span> <a href="/blog/another-post-slug">Another post title</a> <span class="sa-archive-cats">Content Marketing</span></li>
  </ul>

  <h2 id="y-2025">2025</h2>
  <ul class="sa-archive-list">
    <li><span class="sa-archive-date">Dec 09</span> <a href="/blog/older-post-slug">An older post title</a> <span class="sa-archive-cats">Case Study</span></li>
  </ul>

  <!-- Repeat one <h2> per year and one <li> per post. List newest to oldest. -->

</div>

Fill in one heading per year and one line per post, newest to oldest. Yes, the first build is manual, but you only do it once, and a thin Squarespace site has fewer posts than it feels like. The archive only works if Google reaches it, so link to it from three places it crawls constantly: a small "View the full archive" link in your blog header, a "Blog Archive" link in your site footer, and a "See all posts" link wherever your homepage shows recent posts. Then open the page in Search Console URL Inspection and click Request Indexing to give it a first nudge.

Move 2: Add related posts to the bottom of every article

The archive fixes vertical reach from one hub. This move fixes lateral reach, the links that let Google hop sideways from one post directly to related ones instead of walking the pagination chain. It also kills the orphan problem, because every post now both links out and gets linked to.

Squarespace will not do this dynamically on its own, so we use a small script. Go to Settings, then Advanced, then Code Injection, and paste the block below into the Footer field. It runs only on individual post URLs, pulls your post list from Squarespace's own JSON feed, picks the posts that share a category with the one being viewed, and renders six plain anchor tags so Googlebot picks them up on the first paint. If Squarespace ever changes that feed, the block fails silently and shows nothing rather than breaking the page.

<style>
  .sa-related { max-width: 820px; margin: 3rem auto 2rem; padding: 2rem 0 1rem; border-top: 1px solid #eee; font-family: inherit; }
  .sa-related h3 { margin: 0 0 1.25rem; font-size: 1.25rem; font-weight: 600; }
  .sa-related-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.25rem 1.5rem; }
  @media (max-width: 600px) { .sa-related-grid { grid-template-columns: 1fr; } }
  .sa-related-item { display: block; padding: 0.75rem 0; border-bottom: 1px solid #f3f3f3; text-decoration: none; color: inherit; }
  .sa-related-item:hover .sa-related-title { text-decoration: underline; }
  .sa-related-cat { display: block; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: #888; margin-bottom: 0.35rem; }
  .sa-related-title { display: block; font-size: 1rem; font-weight: 500; color: #111; line-height: 1.4; }
</style>

<script>
(function () {
  var path = window.location.pathname;
  if (!/^\/blog\/[^\/]+$/.test(path)) return;
  var currentSlug = path.split('/').pop();

  function escapeHtml(s) {
    return String(s).replace(/[&<>"']/g, function (c) {
      return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
    });
  }

  async function loadAllPosts() {
    var all = [];
    var url = '/blog?format=json-pretty';
    var safety = 20;
    while (url && safety--) {
      try {
        var r = await fetch(url, { credentials: 'omit' });
        if (!r.ok) break;
        var j = await r.json();
        (j.items || []).forEach(function (p) {
          all.push({ title: p.title, slug: p.urlId, categories: p.categories || [], publishOn: p.publishOn });
        });
        url = (j.pagination && j.pagination.nextPage) ? j.pagination.nextPageUrl + '&format=json-pretty' : null;
      } catch (e) { break; }
    }
    return all;
  }

  function pickRelated(all, current) {
    var others = all.filter(function (p) { return p.slug !== current.slug; });
    if (!current.categories.length) {
      return others.sort(function (a, b) { return b.publishOn - a.publishOn; }).slice(0, 6);
    }
    var cats = current.categories;
    var scored = others.map(function (p) {
      var overlap = p.categories.filter(function (c) { return cats.indexOf(c) > -1; }).length;
      return { post: p, overlap: overlap };
    }).filter(function (x) { return x.overlap > 0; })
      .sort(function (a, b) {
        if (b.overlap !== a.overlap) return b.overlap - a.overlap;
        return b.post.publishOn - a.post.publishOn;
      });
    var picked = scored.slice(0, 6).map(function (x) { return x.post; });
    if (picked.length < 6) {
      var fill = others.filter(function (p) { return picked.indexOf(p) === -1; })
        .sort(function (a, b) { return b.publishOn - a.publishOn; })
        .slice(0, 6 - picked.length);
      picked = picked.concat(fill);
    }
    return picked;
  }

  function render(picked) {
    var html = '<section class="sa-related" aria-labelledby="sa-related-h">'
      + '<h3 id="sa-related-h">More from the blog</h3>'
      + '<div class="sa-related-grid">';
    picked.forEach(function (p) {
      var cat = p.categories[0] || '';
      html += '<a class="sa-related-item" href="/blog/' + p.slug + '">'
        + (cat ? '<span class="sa-related-cat">' + escapeHtml(cat) + '</span>' : '')
        + '<span class="sa-related-title">' + escapeHtml(p.title) + '</span>'
        + '</a>';
    });
    html += '</div></section>';

    var target =
      document.querySelector('.blog-item-content-wrapper') ||
      document.querySelector('.blog-item-content') ||
      document.querySelector('article .sqs-layout') ||
      document.querySelector('article') ||
      document.querySelector('main');
    if (!target) return;
    var wrap = document.createElement('div');
    wrap.innerHTML = html;
    target.appendChild(wrap.firstChild);
  }

  function init() {
    loadAllPosts().then(function (all) {
      var current = all.find(function (p) { return p.slug === currentSlug; });
      if (!current) { current = { slug: currentSlug, categories: [] }; }
      var picked = pickRelated(all, current);
      if (picked.length) render(picked);
    }).catch(function () { /* silent */ });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();
</script>

Change "More from the blog" to whatever heading suits your brand. Everything else works as is.

Move 3: Expose every category one click from the blog

Category pages are the natural hubs for clusters of related posts, but they only help if Google can reach them on every crawl. Two small changes put them everywhere.

On the `/blog` page, add or fix a row of category chips so visitors and Googlebot can jump to any topic in one click. If you already have a chip row that is missing categories, this is the common culprit behind half-orphaned topic clusters. Drop a Code Block on the page with the markup below and list every active category.

<div class="sa-blog-chips">
  <a href="/blog" class="sa-chip sa-chip-active">All</a>
  <a href="/blog/category/SEO" class="sa-chip">SEO</a>
  <a href="/blog/category/AI+Search" class="sa-chip">AI Search</a>
  <a href="/blog/category/Content+Marketing" class="sa-chip">Content Marketing</a>
  <a href="/blog/category/Case+Study" class="sa-chip">Case Study</a>
  <!-- Add one line per category. Use + for spaces, exactly as the URL does. -->
</div>

The detail that trips people up: Squarespace encodes spaces in category URLs as a plus sign, so the link for the AI Search category is `/blog/category/AI+Search`, not `/blog/category/AI Search`. Get that wrong and the chip 404s.

Then add the same hubs to your main navigation. Go to Pages, Edit Header, Site Navigation, and add a folder named Topics next to your Blog link with URL items pointing to your top few category pages. Now every page on the site links to your main hubs, which means Googlebot sees them on every crawl, and crawl equity flows down into the clusters instead of dead-ending at the blog index.

Move 4: Stop category pages from looking like duplicates of /blog

This is the move that converts category pages from suspected duplicates into recognized hubs. Because Squarespace renders `/blog` and `/blog/category/X` from one template, the category page shows the same hero text as the main blog. The script below rewrites the eyebrow, headline, and description based on the category in the URL, updates the meta description so the search snippet matches, and injects CollectionPage schema so Google understands the page is a topical hub rather than a copy of `/blog`. It also hides the default hero for a split second on category pages so visitors never see the wrong text flash before the swap.

Paste this into Settings, Advanced, Code Injection, in the Header field. It must go in the Header, not the Footer, because the anti-flicker styling has to load before the page paints. You will need to adjust the three selectors (`.sa-eyebrow-pill`, `.sa-hero-h1`, `.sa-hero-sub`) to match the class names your template uses for those hero elements.

<style>
  html.sa-cat-loading .sa-eyebrow-pill,
  html.sa-cat-loading .sa-hero-h1,
  html.sa-cat-loading .sa-hero-sub { visibility: hidden; }
</style>

<script>
  (function () {
    if (/^\/blog\/category\//.test(location.pathname)) {
      document.documentElement.classList.add('sa-cat-loading');
    }
  })();
</script>

<script>
  (function () {
    var path = location.pathname;
    var m = path.match(/^\/blog\/category\/([^\/?#]+)/);
    if (!m) return;

    var slug = m[1];
    var displayName = decodeURIComponent(slug.replace(/\+/g, ' '));

    // ---- Edit category copy here. Keys use + for spaces, e.g. "AI+Search". ----
    var CATEGORY_COPY = {
      'SEO': {
        eyebrow: 'Topic: SEO',
        h1: 'Everything we have written on ranking, crawling, and earning organic traffic.',
        sub: 'Our full library of SEO essays. Strategy, technical fixes, and the unglamorous work behind durable organic growth.',
        meta: 'Our SEO library. Strategy, technical SEO, and frameworks for earning durable organic visibility.'
      },
      'AI+Search': {
        eyebrow: 'Topic: AI Search',
        h1: 'Field notes on AI search, AEO, and GEO.',
        sub: 'How brands earn citations inside ChatGPT, Perplexity, Gemini, and Google AI Overviews.',
        meta: 'AI search, AEO, and GEO writing. How to earn citations inside ChatGPT, Perplexity, Gemini, and Google AI Overviews.'
      }
      // Add one entry per category, matching the keys in your chip row.
    };
    // ---- End category copy ----

    var copy = CATEGORY_COPY[slug];
    if (!copy) {
      copy = {
        eyebrow: 'Topic: ' + displayName,
        h1: 'Everything we have written on ' + displayName + '.',
        sub: 'A focused collection of essays on ' + displayName + '.',
        meta: 'Writing on ' + displayName + '. Strategy, frameworks, and case studies.'
      };
    }

    function applyCopy() {
      var eyebrow = document.querySelector('.sa-eyebrow-pill');
      var h1 = document.querySelector('.sa-hero-h1');
      var sub = document.querySelector('.sa-hero-sub');

      if (eyebrow) eyebrow.textContent = copy.eyebrow;
      if (h1) h1.textContent = copy.h1;
      if (sub) sub.textContent = copy.sub;

      var metaDesc = document.querySelector('meta[name="description"]');
      if (metaDesc && copy.meta) metaDesc.setAttribute('content', copy.meta);
      var ogDesc = document.querySelector('meta[property="og:description"]');
      if (ogDesc && copy.meta) ogDesc.setAttribute('content', copy.meta);

      try {
        var schema = {
          '@context': 'https://schema.org',
          '@type': 'CollectionPage',
          name: copy.h1,
          description: copy.meta || copy.sub,
          url: location.origin + location.pathname,
          isPartOf: { '@type': 'Blog', name: 'Blog', url: location.origin + '/blog' },
          about: { '@type': 'Thing', name: displayName }
        };
        var script = document.createElement('script');
        script.type = 'application/ld+json';
        script.textContent = JSON.stringify(schema);
        document.head.appendChild(script);
      } catch (e) { /* silent */ }

      document.documentElement.classList.remove('sa-cat-loading');
    }

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', applyCopy);
    } else {
      applyCopy();
    }

    setTimeout(function () {
      document.documentElement.classList.remove('sa-cat-loading');
    }, 1500);
  })();
</script>

One maintenance note worth pinning somewhere: your chip row and your `CATEGORY_COPY` object have to stay in sync. The chip row controls what visitors can click, and the copy object controls what those pages say when they arrive. When you add or rename a category, edit both. Drift between the two is exactly how a broken hub appears later.

Move 5: Clean up legacy slugs

Older Squarespace blogs almost always carry a few ugly URLs. The two we see most often are auto-generated fallback slugs (a random string like `/blog/3a5ig8xhb6nwhhyqzfsliqr1tjzx59`, created when a post was published without a clean URL) and slugs with a leaked HTML entity in them (an `amp` fragment from an ampersand in the title). Both are weak indexation signals and look untrustworthy to a person scanning a search result.

Fix each by opening the post, clicking the gear icon, and editing the URL slug to something clean and descriptive. Squarespace automatically 301 redirects the old URL to the new one, so you keep whatever equity the old URL had. Confirm by visiting both URLs after saving.

What to expect, and when

Structural fixes are not instant. Google has to recrawl your hubs, follow the new links, and re-evaluate the posts it reaches. Here is the realistic curve. We are early in it ourselves, so treat this as the timeline to watch on your own Search Console Pages report rather than a promise.

Timeline What to expect
Week 1 The archive page gets indexed. A manual URL inspection of five to ten priority old posts shows them recrawled.
Weeks 3 to 4 The first batch of previously deindexed posts reappears in the Indexed report.
Weeks 6 to 8 The main recovery wave. Posts with strong topical links from newer content move first.
Week 12 Steady state. Anything still not indexed after this is a content quality issue, not a structural one, and should be refreshed or pruned.

Use the Pages report as your scoreboard and watch the Indexed and Not indexed counts move week over week. If nothing has shifted by week four, the remaining problem is content quality or a duplicate canonical signal, not crawl access, and the diagnosis changes.

The underlying principle

Squarespace is a genuinely good platform for publishing, but its blog architecture optimizes for the reader who lands on your newest post, not for the crawler trying to reach your hundredth. Left at its defaults, it quietly turns your back catalog into a chain of half-orphaned pages that Google slowly forgets.

Every fix above is a variation on one idea: shorten the distance between any given post and a page Google crawls often, and make your category pages distinct enough to count as hubs. Do that and indexation stops being something you chase one URL at a time in the inspection tool. It becomes a property of how your site is built.

If you have looked at your own Pages report and the not-indexed pile is bigger than you expected, that is the symptom we are describing, and structure is almost certainly the cause. We help brands fix exactly this kind of technical SEO problem at scale, if you would rather hand it off than work through it yourself.

Previous
Previous

How to Get Your Brand Cited in ChatGPT, Gemini and Perplexity

Next
Next

ChatGPT, Gemini and Perplexity for Business: What They Are and How to Use Them