feat: replace blog pagination with "Load More" button and optional auto-scroll, improve noscript support by stasadev · Pull Request #557 · ddev/ddev.com

@stasadev @claude

## The Issue

Blog post listings used prev/next pagination links requiring full page
navigations to browse content. Tables in blog posts also lacked full-width
rendering and horizontal scroll on mobile. Search results were capped at 10
with no way to see more, and did not restore when navigating back.

## How This PR Solves The Issue

**Infinite scroll for blog listings**

Replaces pagination with a fetch-HTML-fragment approach. Each listing page
(index, category, author) renders its first 12 posts normally. A
`data-next-url` attribute on the grid container points to the next pre-built
static page. An `IntersectionObserver` fires when the user scrolls near the
bottom, fetches that URL, extracts the post cards via `DOMParser`, and appends
them to the current grid. The next `data-next-url` is then read from the
fetched page to continue the chain.

This keeps every page's initial payload at 12 posts regardless of total post
count, preserves full Astro image optimization on all cards (no client-side
re-rendering), and leaves all paginated static routes intact as valid URLs and
noscript fallbacks.

All logic is centralised in a new `BlogPostGrid` component. `Paging.astro` is
removed as it is no longer referenced anywhere.

**Infinite scroll for search**

Search results now load progressively in batches of 10 as the user scrolls,
with all matches held in memory after each query. Previously results were
hard-capped at 10. The input value is also checked on page initialisation so
results are restored correctly after browser back navigation.

**Full-width tables with mobile scroll**

`overflow-x: auto` on a `<table>` element has no effect because tables are
not block containers. The previous workaround (`display: block`) broke the
table's natural full-width behaviour. A new rehype plugin
(`rehype-wrap-tables.mjs`) wraps each `<table>` in a `<div class="table-wrapper">`
at build time, keeping the table as `display: table` (full width via Tailwind
Typography) while the block-level wrapper provides the scroll container.

## Manual Testing Instructions

- Visit /blog/ and scroll down to verify posts load without page navigation
- Visit /blog/category/<category>/ and /blog/author/<author>/ and scroll
- Check Network tab: each scroll fetch returns 200, not 301
- Disable JS and confirm the noscript fallback link is visible
- Search for a term with many results and scroll to load beyond the first 10
- Navigate to a result, press back, confirm results are restored
- Open a blog post with a wide table and verify it is full width on desktop
  and scrolls horizontally on mobile

## Automated Testing Overview

No automated tests added; all changes are client-side JS and build-time HTML
transforms.

## Release/Deployment Notes

All paginated static routes (/blog/2/, /blog/category/x/2/, etc.) remain
valid URLs. No redirects or deployment config changes needed.

🤖 Developed with assistance from [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

rfay

@stasadev

@stasadev @claude

## Blog listing
- Replace infinite scroll with a "Load More" button, fixing the
  inaccessible footer problem
- Button is hidden by default, shown only when JS is available
- Button matches the site's hollow button style with dark mode support

## AnimatedTerminal
- Add noscript fallback: pre-render ddev describe output at build time
- Use CSS (data-theme attribute set by inline head script) to toggle
  between animated and static pre elements with zero flash
- Deduplicate describeOutput — defined once in frontmatter, passed via
  data attribute, read by the animation script

## get-started page
- Replace noscript message with CSS :has() rules so platform picker
  works without JS (clicking labels shows the correct platform section)

## global.css
- Hide copy buttons for noscript users (html:not([data-theme]))
- Add noscript notice for unrendered Mermaid diagrams
- Remove noscript dark mode CSS (not supported without JS)

🤖 Developed with assistance from [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

@stasadev stasadev changed the title feat: replace blog pagination with infinite scroll feat: replace blog pagination with "Load More" and improve noscript support

Feb 25, 2026

@stasadev

@stasadev @claude

Adds a persistent "Auto-load on scroll" checkbox to all blog listing pages.
Preference is saved to localStorage so it applies across visits. Defaults
to "Load More" button mode. Toggle is always visible regardless of whether
a next page exists, allowing preference to be set on short category pages.

🤖 Developed with assistance from [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

@stasadev stasadev changed the title feat: replace blog pagination with "Load More" and improve noscript support feat: replace blog pagination with "Load More" button and optional auto-scroll, improve noscript support

Feb 25, 2026

@stasadev stasadev deleted the 20260224_stasasdev_infinite_scroll branch

February 26, 2026 10:13