Search engines and pictures
An App that lets signed-out visitors read records (auth.public) is found by search engines and shared as links. tablewalk serves those pages ready to read: there is nothing to configure.
public: { read: ['property', 'property_photo', 'unit'], fields: { property: { read: ['id', 'name', 'address', 'city', 'state', 'zip', 'kind', 'year_built', 'description'] }, // …See it in the sample (examples/apps/lintel/app.ts:127): signed out, the buildings, their photos and what a listing says of a home; never who lives there.
With that grant, Lintel’s public pages are served like this:
- Pages arrive drawn. A public list, a record, the landing and a public form come with their content already in the HTML, so a crawler or a reader without scripts sees the homes, their rents and their facts. The App takes the page over when it starts, and nothing on screen moves. Signed-in pages are drawn in the browser as before.
- Every page names itself. A public page carries its title, a
<link rel="canonical">, and Open Graph and Twitter tags (title, description, picture), so a shared link previews with the home’s name and picture. /sitemap.xmllists every public address: the lists, and each record a visitor can open, under the addresstablewalk check --routesprints. Past 10,000 addresses it becomes an index of parts. Addresses use the host the crawler asked on, so a white-label domain gets its own./robots.txtkeeps crawlers out of the API, allows the pictures and share cards, and points to the sitemap. A server with no public App asks crawlers to stay out entirely.
What a visitor can read decides what any of these say. A row or field the public grant and row policy hide never appears in the HTML, the sitemap, or a preview.
Share cards
Section titled “Share cards”A public list, a record without a photo, and the landing preview with a 1200×630 card drawn by the server: the page’s title and description and the site’s name, in the App’s look (its ground, ink, accent and ui.logo), set in Inter. A record with a photo previews with its photo.
GET /api/og?path=%2Flistings&v=3f9c…
200 Content-Type: image/png Cache-Control: public, max-age=31536000, immutable ETag: "3f9c…"The page names the address. v is a hash of everything on the card, so a changed title names a new card. The route reads the page as a signed-out visitor, whoever asks, so a card shows nothing the page does not. On a white-label or tenant host the card wears that host’s published branding.
Cards are drawn with satori and resvg when both are installed (npm install satori @resvg/resvg-js). Without them a page previews with its first photo, or with no picture.
Pictures at the size they are drawn
Section titled “Pictures at the size they are drawn”Cards, galleries, record photos and a landing’s hero ask for their file pictures at the width they are drawn (srcset). The file route makes each width once, in the format the browser prefers:
GET /api/file?table=property_photo&key=%7B%22id%22%3A12%7D&inline=1&w=640Accept: image/avif,image/webp,*/*
200 Content-Type: image/avif Vary: Cookie, Acceptw is one of 160, 320, 480, 640, 960, 1280 or 1920; any other width is refused with a 400. A picture is resized only after the same read as without w, so the App’s grants and row policy apply at every size. Resizing uses sharp when it is installed (npm install sharp). Without it, every width sends the original.
Resized copies are kept on disk, up to 256 MB, and the least recently sent are removed first. Set TABLEWALK_IMAGE_CACHE to the folder they live in. Unset, the server makes a private folder of its own under the system’s temp folder (readable by its user alone) and removes it when it stops, so copies do not outlive a run; name a folder to keep them across restarts, and keep it to the server’s user.
Turning it off
Section titled “Turning it off”Set TABLEWALK_SSR=0 to send public pages without their drawn content. The browser draws them, as it does every signed-in page. Titles, canonical links, preview tags and the sitemap stay.