![SEO and Server Rendering](https://cdn.slatesource.com/3/0/2/3028abfd-900f-4349-8230-84fc14c32830.webp)

# SEO and Server Rendering

- [Made in Slatesource](https://slatesource.com/@steph/seo-and-server-rendering)
- By [Steph](https://slatesource.com/@steph)
- Created on Jul 19, 2026

Put .md on the end of this page's address and the server hands back the same content as plain markdown. Nothing was exported and nothing is generated ahead of time. Try it on any public slate.

[This page, as markdown](https://slatesource.com/@steph/seo-and-server-rendering.md?utm_source=slatesource)

## RenderedRendered BeforeBefore It Is RequestedRequested

A slateslate pagepage is fetchedfetched from the backendbackend and renderedrendered to HTMLHTML in ExpressExpress beforebefore ReactReact everever runsruns in a browserbrowser. A crawlercrawler getsgets the finishedfinished articlearticle; a personperson getsgets the samesame HTMLHTML and then hydrateshydrates on toptop of it. It is a synchronoussynchronous stringstring renderrender, not streamingstreaming, and no pagepage HTMLHTML is cachedcached at all. AssetsAssets getget a yearyear, the sitemapsitemap getsgets an hourhour, pagespages getget nothingnothing. For a cataloguecatalogue where any slateslate can be editededited at any momentmoment that is the safesafe defaultdefault, and it is a realreal costcost paidpaid deliberatelydeliberately.

## The MarkdownMarkdown Is Not a SerializerSerializer

This is the partpart that surprisessurprises peoplepeople who looklook. You wouldwould expectexpect the .md routeroute to walkwalk the chipschips and writewrite markdownmarkdown for eacheach oneone. It does not. It rendersrenders the fullfull ReactReact pagepage to HTMLHTML exactlyexactly as a browserbrowser wouldwould receivereceive it, selectsselects the chipchip gridgrid out of that HTMLHTML, and convertsconverts the resultresult with TurndownTurndown. The markdownmarkdown is downstreamdownstream of the renderingrendering, not parallelparallel to it.

typescript

1

2

3

4

5

6

```
export const htmlToMarkdown = (html: string): string => {
  const $ = cheerio.load(html);
  const mainContent = $('[data-liquid-grid]').html();
  return td.turndown(mainContent ?? html);
};
```

## Why That WayWay RoundRound

A parallelparallel serializerserializer is a secondsecond implementationimplementation of everyevery chipchip, and the secondsecond oneone alwaysalways driftsdrifts. GoingGoing throughthrough the renderedrendered DOMDOM meansmeans a newnew chipchip is readablereadable by a languagelanguage modelmodel the dayday it shipsships, with no extraextra workwork and no possibilitypossibility of the twotwo versionsversions disagreeingdisagreeing. The costcost is that anythinganything the chipchip expressesexpresses visuallyvisually ratherrather than textuallytextually disappearsdisappears. ScriptsScripts, buttonsbuttons, inputsinputs and everyevery SVGSVG are strippedstripped, so the MapMap, ShaderShader and StatsStats chipschips leaveleave nothingnothing behindbehind. ImagesImages comecome out with emptyempty altalt texttext, becausebecause the componentcomponent passespasses an emptyempty altalt.

## SixSix ChipsChips GetGet Hand-WrittenHand-Written RulesRules

Where the genericgeneric conversionconversion producedproduced somethingsomething poorpoor, a specificspecific rulerule was addedadded, keyedkeyed off a datadata attributeattribute the componentcomponent alreadyalready emitsemits. CounterCounter becomesbecomes a boldbold numbernumber and a labellabel. LinkLink becomesbecomes a properproper markdownmarkdown linklink. QuoteQuote and NoteNote becomebecome blockquotesblockquotes. TaskTask becomesbecomes a checkboxcheckbox with its deadlinedeadline. DurationDuration becomesbecomes a dateddated rangerange. EverythingEverything elseelse fallsfalls throughthrough to whateverwhatever its markupmarkup happenshappens to convertconvert to.

## OneOne of the SixSix Has NeverNever FiredFired

The NoteNote rulerule matchesmatches an asideaside elementelement carryingcarrying a chipchip attributeattribute. The NoteNote chipchip rendersrenders twotwo plainplain divsdivs with neitherneither. The rulerule is syntacticallysyntactically finefine, sitssits in the filefile nextnext to fivefive that workwork, and has nevernever onceonce matchedmatched a nodenode. NoteNote chipschips have alwaysalways gonegone throughthrough the genericgeneric pathpath. NothingNothing about the outputoutput lookslooks brokenbroken, whichwhich is exactlyexactly why it survivedsurvived.

The indexing work, in order16.03.2026 – 08.07.2026

16.03.2026Slates start appearing in a dynamically generated sitemap.

16.03.2026

20.03.2026The .md extension ships for LLM consumption and clean exports.

20.03.2026

29.03.2026Canonical link tags added to every server-rendered page.

29.03.2026

03.04.2026Markdown extended to sub-pages.

03.04.2026

25.04.2026llms.txt published for crawler discovery.

25.04.2026

29.04.2026robots.txt starts naming AI crawlers explicitly.

29.04.2026

08.07.2026A fallback that writes crawlable HTML straight from chip data.

08.07.2026

## The Sub-PageSub-Page ClaimClaim Is HalfHalf TrueTrue

The AprilApril entryentry sayssays markdownmarkdown worksworks on sub-pagessub-pages. On the numericnumeric formform of a URLURL it does. On the canonicalcanonical formform it does not: the patternpattern that detectsdetects the extensionextension has no groupgroup for a pathpath segmentsegment afterafter the slugslug, so the addressaddress is readread as a pagepage namednamed with a .md on the endend and servedserved as HTMLHTML. No errorerror, no warningwarning, just the wrongwrong contentcontent typetype. It has been that wayway sincesince the entryentry was writtenwritten.

Sitemap row cap

50000

Sitemap cache

1 hour

Meta description cut at

160 characters

Chips with a hand-written markdown rule

6

Page HTML cache

none

## The FallbackFallback NobodyNobody SeesSees

If the ReactReact renderrender throwsthrows, the pagepage wouldwould shipship an emptyempty bodybody and a crawlercrawler wouldwould indexindex nothingnothing. So therethere is a secondsecond pathpath that buildsbuilds plainplain HTMLHTML directlydirectly from the chipchip datadata: a headingheading, the authorauthor, then eacheach chipchip in rowrow orderorder as paragraphsparagraphs, blockquotesblockquotes, listslists and preformattedpreformatted blocksblocks. It is uglieruglier than the realreal pagepage and it is only everever servedserved when the realreal oneone failedfailed. It handleshandles about sixteensixteen chipchip typestypes and returnsreturns nothingnothing for the restrest.

## Where the HeadHead ContradictsContradicts ItselfItself

WorthWorth writingwriting downdown ratherrather than quietlyquietly fixingfixing beforebefore publishingpublishing this. The canonicalcanonical tagtag on a slateslate pointspoints at the /@useruser/slugslug formform. The structuredstructured datadata in the samesame documentdocument stillstill pointspoints at the oldold /u/ formform, whichwhich the samesame serverserver 301s301s awayaway. And oneone slateslate typetype mapsmaps to the literalliteral stringstring TBDTBD insteadinstead of a realreal schemaschema.orgorg typetype, whichwhich has been shippingshipping in productionproduction JSON-LDJSON-LD sincesince the mappingmapping was writtenwritten. BothBoth are smallsmall. BothBoth are the kindkind of thingthing you only findfind by readingreading your ownown outputoutput.

## What This Is WorthWorth to a BuilderBuilder

A projectproject pagepage that rendersrenders on the serverserver, sitssits in a sitemapsitemap, and answersanswers in markdownmarkdown when a modelmodel asksasks for it is a projectproject pagepage that is stillstill legiblelegible in fivefive yearsyears to readersreaders you did not anticipateanticipate. NoneNone of that requiredrequired writingwriting a sitesite. It requiredrequired puttingputting the contentcontent somewheresomewhere that alreadyalready does it.

[The sitemap, generated on request](https://slatesource.com/sitemap.xml?utm_source=slatesource)