Skip to content

Ads and sponsors

Markdown

Ads are static markup: sold by email, written into site.config.ts, rendered at build time, with no script and no ad network. Each placement is one boolean under ads, and ads.page.enabled builds the /advertise/ page that lists the rates.

  1. Turn on the page and one placement.

    site.config.ts
    ads: {
    contactEmail: 'ads@acme.com',
    page: { enabled: true, note: 'Placements run for a calendar month.' },
    topBanner: { enabled: true, text: 'Sponsor Acme — your product in front of every reader.', url: 'https://sponsor.example' },
    rates: [{ slot: 'topBanner', price: '$99 / month', blurb: 'One line under the header, on every page.' }],
    },

    pnpm check:config answers site.config.ts OK — <name> (<url>). A url or logo must be an https:// URL or a /-rooted path.

  2. Build and deploy.

    Terminal
    pnpm deploy

    The build’s zero-js: line still ends — ok: nothing here adds a script.

What to check Where You should see
The placement curl -s https://<your domain>/ | grep -o 'data-ad="[a-z-]*"' data-ad="top-banner"; the others are sponsors, inline-card, corner
The rates https://<your domain>/advertise/ the table with a live badge on each enabled placement, and an Email button
The footer any page an Advertise link
The rel page source every ad link carries rel="sponsored noopener"
Placement Where it renders What marks it
topBanner one line between the header and the content, on every public page the label pill (default Ad), the text, a linkLabel button
sponsors a logo wall under the home-page hero, above the featured listings; renders nothing while items is empty the heading (default “Supported by these partners”), each logo titled with its tagline, and an Advertise with us link when the page is on
inlineCard a card shaped like a listing inside every listing grid, after every every listings, never as the last cell “Sponsored” under the name, an Ad pill, a dashed border
corner a small card fixed bottom-right on wide screens (the md breakpoint and up) an Ad pill and a × that hides it for that page through a CSS checkbox, no script

The demo home page with the top banner under the header, the sponsor wall under the hero, and the corner card bottom right

Every ad anchor is rel="sponsored noopener", so search engines read it as what it is. A logo renders with an empty alt and lazy loading; the in-grid card without a logo shows the name’s first letter.

/advertise/ is built only while ads.page.enabled is true — the route has an empty getStaticPaths() otherwise, so there is no stub — and advertise is a reserved segment no listing or category base can take. It renders page.intro, a table of rates (placement, where it appears plus the rate’s blurb, price) with a live badge on every placement currently on, page.note, and a mailto: button to ads.contactEmail, falling back to site.contactEmail, with the subject Advertising on <site name>. The footer links to it from the same switch, and so does the sponsor wall.

The demo’s /advertise/ page: the placements table with prices and live badges, and the Email button

Selling one. A prospect writes to the contact address naming a placement and a duration. You add them to site.config.ts — a sponsor is name, url, optional logo and tagline; the other placements take name, tagline, url, logo — and deploy. There is no runtime state: the placement runs until you edit the file again.

The demo turns every placement on with placeholder copy pointing at /advertise/, priced per month, so a buyer sees each slot before selling it. Treat those values as an example, not a price list.

State Visitor sees /admin/ reports Fix
Everything off (the default) no ads, no footer link, /advertise/ is a 404 nothing — ads have no runtime
A placement on, page.enabled off the ad links to /advertise/, which does not exist, unless url is set nothing set url, or enable the page
sponsors.enabled with empty items nothing renders; not marked live on the rates table nothing add an item
A grid no longer than every no in-grid card nothing expected
A url without https:// or a leading / pnpm check:config fails naming the field fix the value
Field Default What it changes
ads.contactEmail site.contactEmail the mailto: on /advertise/
ads.page.enabled false whether /advertise/ and its footer link exist
ads.page.intro, ads.page.note a stock intro, no note the copy above and below the rates
ads.topBanner, ads.sponsors, ads.inlineCard, ads.corner enabled: false one placement each; inlineCard.every is 2 to 50, default 6
ads.rates one On request row per placement the pricing table: slot, price, blurb

See ads.

  • Directorysrc/
    • Directorycomponents/ads/
      • AdBanner.astro topBanner
      • Sponsors.astro sponsors
      • AdCard.astro inlineCard
      • CornerAd.astro corner
    • lib/ads.ts AD_SLOTS, activeSlots(), withInlineSlots()
    • pages/[advertise].astro the rates page
    • layouts/Base.astro mounts the banner and the corner card
    • templates/HomePage.astro mounts the sponsor wall
    • components/listing/Grid.astro mounts the in-grid card
    • components/site/Footer.astro the Advertise link
    • config/defaults.ts DEFAULT_ADS
  • site.config.ts the demo’s ads block