Skip to content

feat(examples): add streaming-ssr example with 5 pages#37

Merged
Asjas merged 5 commits intomainfrom
copilot/create-streaming-ssr-starter-example
Mar 23, 2026
Merged

feat(examples): add streaming-ssr example with 5 pages#37
Asjas merged 5 commits intomainfrom
copilot/create-streaming-ssr-starter-example

Conversation

Copy link
Contributor

Copilot AI commented Mar 23, 2026

Adds examples/streaming-ssr/ — a second example app focused exclusively on the streaming SSR rendering pipeline, with no database or auth layers to obscure the core pattern.

Structure

  • src/server.ts — 5 named page routes each using createStreamingSSRHandler() with per-route getProps, plus a catch-all. Runs on port 3001 to avoid colliding with the starter.
  • src/renderer/worker.ts — re-exports @scratchyjs/renderer/worker (same pattern as starter).
  • src/config.ts — thin alias over core Config; no extra env vars needed.

Pages (5 Qwik City routes)

Route Content
/ Hero, framework stats, feature highlights
/about Mission, story, values, team
/features 6 feature cards with category badges
/blog 5 articles with tags, author, reading time
/contact Contact channels + client-side feedback form

Shared layout.tsx provides a nav bar with active-link highlighting across all pages.

Key difference from examples/starter

// starter — collects full HTML then sends
server.get("/*", createSSRHandler());

// streaming-ssr — streams ordered chunks via chunked transfer encoding,
// with page-specific props passed to the worker
server.get("/blog", createStreamingSSRHandler({
  getProps: () => ({ page: "blog", posts: BLOG_POSTS }),
}));

Tests

12 integration tests cover: all 5 page routes return valid HTML, x-content-type-options: nosniff on every route, HTML-escaped props embedded in the __PROPS__ script block, CORS preflight, and health check.

Original prompt

create a second example. make it a streaming ssr starter. and create more pages, 4 or 5 different pages.

[Chronological Review: The conversation began with the user attempting to run an example app using pnpm, which resulted in a module not found error. The user then inquired about missing drizzle commands for generating and migrating databases. They expressed concerns about the drift between the example app and the expected behavior of the `create-scratchy-app` package. The user requested patches and fixes for migration issues, updated environment variables, and raised questions about database existence and configuration. They also reported issues with rendering content and JSX types, and requested to kill the server on port 3000. The user expressed frustration over the lack of certain secrets in the configuration and the need for specific formatting rules in Prettier. The conversation culminated in a request to create a second example app with multiple pages.]

[Intent Mapping:

  1. "i am trying to run the example app in the terminal." - User seeks to execute an example app.
  2. "why are there no drizzle commands?" - Inquiry about missing commands for database management.
  3. "are all of these steps a part of the create-scratchy-app package?" - User wants to understand the relationship between the example app and the package.
  4. "yes patch it. also fix this." - Request for immediate fixes to migration issues.
  5. "it needs to be scratchy_example." - User specifies a required database name.
  6. "why are the migrations still failing?" - User seeks clarification on migration failures.
  7. "can't there be more than 1 database in postgres?" - Inquiry about PostgreSQL capabilities.
  8. "DO IT STOP TELLING ME YOU CAN DO IT????" - User expresses frustration and demands action.
  9. "and what about the PEPPER_SECRET I ASKED ABOUT EARLIER?" - User inquires about the inclusion of a specific secret.
  10. "I do not want the drizzle files to be committed from the starter app." - User requests to exclude certain files from git commits.
  11. "Does the page source seem correct?" - User seeks validation of HTML structure.
  12. "create a second example. make it a streaming ssr starter." - User requests the creation of a new example app with multiple pages.]

[Technical Inventory:

  • Technologies: pnpm, PostgreSQL, Prettier, TypeScript, JSX.
  • Frameworks: Scratchy (SSR framework).
  • Tools: drizzle-kit for database migrations, tsx for running TypeScript files.
  • Configuration files: .env, drizzle.config.ts, .prettierignore.]

[Code Archaeology:

  • Files discussed: /home/codewizard/git/scratchyjs/examples/starter/src/index.ts, /home/codewizard/git/scratchyjs/examples/starter/drizzle/config.js, /home/codewizard/git/scratchyjs/.prettierignore.
  • Key functions: Migration commands, server rendering logic, Prettier formatting rules.]

[Progress Assessment:

  • Completed Tasks: User has updated the database .env value and requested patches for migration issues.
  • Partially Complete Work: User is still facing issues with rendering and database configurations, and the creation of a second example app is pending.]

[Context Validation: All critical information for continuation is captured, including user frustrations, specific requests for fixes, and the need for additional examples.]

[Recent Commands Analysis:

  • Last Agent Commands: User executed pnpm commands to run the dev server and migrate the database.
  • Tool Results Summary: Errors related to missing modules and database configurations were reported, including "Cannot find module" and "database does not exist."
  • Pre-Summary State: The user was actively working on debugging rendering issues and configuring the application when the token budget was exceeded.
  • Operation Context: These commands were executed to address user goals of running the example app and ensuring proper database migrations.]
1. Conversation Overview: - Primary Objectives: User aims to run an example app, fix migration issues, validate configurations, and create a new example app with multiple pages. - Session Context: The conversation has evolved from running an example app to addressing various technical issues, including database configurations, rendering problems, and Prettier formatting rules. - User Intent Evolution: The user has shifted from seeking help with running the app to demanding specific fixes and enhancements, reflecting increasing frustration with the development process.
  1. Technical Foundation:
  • Node.js: Version 25.8.1, used for running the application.
  • PostgreSQL: Database management system, with issues related to database existence and configurations.
  • Prettier: Code formatting tool, with specific requests to ignore certain files.
  • Scratchy: SSR framework for rendering applications.
  1. Codebase Status:
  • /home/codewizard/git/scratchyjs/examples/starter/src/index.ts:
    • Purpose: Entry point for the example app.
    • Current State: Encountering module n...

Created from VS Code.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 23, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 97.8%
🟰 ±0%
2009 / 2054
🔵 Statements 97.26%
🟰 ±0%
2097 / 2156
🔵 Functions 98.51%
🟰 ±0%
399 / 405
🔵 Branches 91.93%
🟰 ±0%
1105 / 1202
File CoverageNo changed files found.
Generated in workflow #306 for commit 79b2b42 by the Vitest Coverage Report Action

Copilot AI and others added 2 commits March 23, 2026 16:11
Create `examples/streaming-ssr/` — a focused second example demonstrating
HTTP streaming SSR with the Scratchy framework.

Key features:
- Uses `createStreamingSSRHandler()` for all page routes instead of
  the non-streaming `createSSRHandler()` from the starter example
- No database or auth dependencies — focused purely on the streaming
  rendering pipeline
- 5 Qwik City client routes: Home, About, Features, Blog, Contact
- Per-route `getProps` showing how to pass server-side data to the worker
- Shared layout with active-link highlighting for all 5 nav links
- 12 integration tests covering streaming HTML, props embedding, CORS,
  health check, and all 5 page routes
- `README.md` with ASCII sequence diagram explaining the streaming flow

Co-authored-by: Asjas <3828967+Asjas@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Asjas/scratchyjs/sessions/887b4a0a-7c81-4104-ab1c-62e40bc63b26
The worker HTML-escapes the JSON props it embeds in the script block
to prevent XSS. The test now checks for the HTML-escaped form
(&quot;page&quot; etc.) directly instead of trying to re-parse the
escaped JSON, which avoids the CodeQL js/double-escaping alert.

Co-authored-by: Asjas <3828967+Asjas@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Asjas/scratchyjs/sessions/887b4a0a-7c81-4104-ab1c-62e40bc63b26
Copilot AI changed the title [WIP] Add second streaming SSR starter example with multiple pages feat(examples): add streaming-ssr example with 5 pages Mar 23, 2026
Copilot AI requested a review from Asjas March 23, 2026 16:20
@Asjas Asjas marked this pull request as ready for review March 23, 2026 16:20
Copilot AI review requested due to automatic review settings March 23, 2026 16:20
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new examples/streaming-ssr/ workspace package to demonstrate Scratchy’s streaming SSR route handler pattern (Fastify + Piscina worker pool), alongside a richer set of Qwik City route source files for a multi-page example.

Changes:

  • Added examples/streaming-ssr/ example app wired to createStreamingSSRHandler() with five named routes plus a catch-all.
  • Added integration tests validating HTML responses, security headers (x-content-type-options), escaped embedded props, CORS behavior, and health check.
  • Updated pnpm-lock.yaml to include the new workspace importer and associated dependency graph changes.

Reviewed changes

Copilot reviewed 19 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds the new workspace importer (examples/streaming-ssr) and updates dependency snapshots.
examples/streaming-ssr/vite.config.ts Vite config using createScratchyConfig() with Tailwind enabled and backend proxy settings.
examples/streaming-ssr/tsconfig.json Server-side TS config for the new example (mirrors starter example pattern).
examples/streaming-ssr/src/types/fastify.d.ts Pulls in renderer Fastify module augmentations for runTask/piscina.
examples/streaming-ssr/src/server.ts Builds the Fastify server, registers renderer plugin + streaming SSR routes with per-route props.
examples/streaming-ssr/src/server.test.ts Integration tests covering health, headers, streaming SSR responses, props escaping, and CORS.
examples/streaming-ssr/src/renderer/worker.ts Piscina worker entrypoint re-exporting @scratchyjs/renderer/worker.
examples/streaming-ssr/src/index.ts Main entrypoint: loads config, builds server, sets up shutdown, listens on configured host/port.
examples/streaming-ssr/src/config.ts Thin wrapper around @scratchyjs/core config loading/typing.
examples/streaming-ssr/src/client/tsconfig.json Client TS config for Qwik routes/components (currently minimal).
examples/streaming-ssr/src/client/styles/global.css Tailwind entry + base layer global styles.
examples/streaming-ssr/src/client/routes/layout.tsx Shared layout with navigation and active-link highlighting.
examples/streaming-ssr/src/client/routes/index.tsx Home page Qwik route + loader providing hero/stats/highlights.
examples/streaming-ssr/src/client/routes/features/index.tsx Features page route with loader data and detailed feature cards.
examples/streaming-ssr/src/client/routes/contact/index.tsx Contact page route with contact channels + client-side-only form state.
examples/streaming-ssr/src/client/routes/blog/index.tsx Blog listing route with loader-provided posts/tags metadata.
examples/streaming-ssr/src/client/routes/about/index.tsx About page route with loader-provided story/values/team sections.
examples/streaming-ssr/public/robots.txt Allows indexing (simple robots.txt).
examples/streaming-ssr/package.json Declares the new example workspace package, dependencies, and dev script.
examples/streaming-ssr/README.md Documentation for the new example, route list, and setup instructions.
examples/streaming-ssr/.env.example Default environment variables for running the example on port 3001.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

@github-actions
Copy link
Contributor

github-actions bot commented Mar 23, 2026

📚 Docs preview deployed

https://scratchyjs-docs-pr-37.asjas.workers.dev

Updates automatically on every push to this PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 23, 2026

⚡ Benchmark Results

benchmarks/renderer/ring-buffer.bench.ts
Suite — Benchmark ops/sec mean (µs) p99 (µs) ±rme vs main
SharedRingBuffer – small payload (64 B) — write 64 bytes 552.5 K 0.0018 0.0074 ±0.96% ~0%
SharedRingBuffer – small payload (64 B) — write + read 64 bytes 481.8 K 0.0021 0.0087 ±0.52% -2.3%
SharedRingBuffer – medium payload (1 KB) — write 1 KB 276.5 K 0.0036 0.0162 ±0.57% -4.6%
SharedRingBuffer – medium payload (1 KB) — write + read 1 KB 238.7 K 0.0042 0.0221 ±0.53% +12.7% 🚀
SharedRingBuffer – large payload (16 KB) — write 16 KB 38.8 K 0.0258 0.0486 ±0.58% -16.6% ⚠️
SharedRingBuffer – large payload (16 KB) — write + read 16 KB 86.9 K 0.0115 0.0452 ±2.64% -4.3%
SharedRingBuffer – sequential throughput (100 × 64 B) — 100 write + read cycles 39.4 K 0.0254 0.0576 ±0.48% -4.6%
SharedRingBuffer – introspection — availableToRead 14.69 M 0.0001 0.0001 ±0.24% ~0%
SharedRingBuffer – introspection — isEmpty 14.98 M 0.0001 0.0001 ±0.09% ~0%
SharedRingBuffer – introspection — isFull 14.47 M 0.0001 0.0001 ±0.21% -1.9%
benchmarks/renderer/shared-buffer.bench.ts
Suite — Benchmark ops/sec mean (µs) p99 (µs) ±rme vs main
SharedBuffer – allocation — createSharedBuffer(4 KB) 642.2 K 0.0016 0.0064 ±0.64% +10.7% 🚀
SharedBuffer – allocation — createSharedBuffer(64 KB) 43.9 K 0.0228 0.0425 ±17.98% +5.1%
SharedBuffer – small payload round-trip — write small JSON 336.0 K 0.0030 0.0127 ±0.41% +10.5% 🚀
SharedBuffer – small payload round-trip — write + read small JSON 245.1 K 0.0041 0.0132 ±1.30% +5.4%
SharedBuffer – medium payload round-trip — write medium JSON (~2 KB) 100.8 K 0.0099 0.0235 ±0.38% +3.7%
SharedBuffer – medium payload round-trip — write + read medium JSON (~2 KB) 56.7 K 0.0176 0.0427 ±0.44% ~0%
SharedBuffer – large payload round-trip — write large JSON (~10 KB) 15.4 K 0.0648 0.1092 ±0.40% ~0%
SharedBuffer – large payload round-trip — write + read large JSON (~10 KB) 7.5 K 0.1333 0.2503 ±0.53% -1.8%
benchmarks/utils/ip-address.bench.ts
Suite — Benchmark ops/sec mean (µs) p99 (µs) ±rme vs main
getClientIPAddress – no IP headers — no IP-related headers → null 2.68 M 0.0004 0.0007 ±1.10% ~0%
getClientIPAddress – single header — cf-connecting-ip (Cloudflare) 1.72 M 0.0006 0.0010 ±0.30% +1.7%
getClientIPAddress – single header — x-forwarded-for (simple) 1.95 M 0.0005 0.0009 ±1.76% +1.5%
getClientIPAddress – single header — x-real-ip 1.50 M 0.0007 0.0012 ±1.22% ~0%
getClientIPAddress – single header — true-client-ip (Akamai / Cloudflare Enterprise) 1.57 M 0.0006 0.0010 ±1.20% +5.0%
getClientIPAddress – x-forwarded-for multi-hop — 2-hop chain 1.83 M 0.0005 0.0008 ±3.18% -1.3%
getClientIPAddress – x-forwarded-for multi-hop — 4-hop chain 1.69 M 0.0006 0.0009 ±0.37% +3.9%
getClientIPAddress – Forwarded header (RFC 7239) — simple for= directive 1.01 M 0.0010 0.0014 ±1.69% -1.0%
getClientIPAddress – Forwarded header (RFC 7239) — for= with port 758.3 K 0.0013 0.0021 ±0.37% +1.6%
getClientIPAddress – Forwarded header (RFC 7239) — IPv6 literal 765.9 K 0.0013 0.0022 ±1.33% +2.5%
getClientIPAddress – Forwarded header (RFC 7239) — multi-hop Forwarded 846.2 K 0.0012 0.0019 ±0.35% ~0%
getClientIPAddress – IPv6 addresses — x-forwarded-for IPv6 1.17 M 0.0009 0.0014 ±1.48% +2.6%
benchmarks/utils/promise.bench.ts
Suite — Benchmark ops/sec mean (µs) p99 (µs) ±rme vs main
promiseHash – concurrent resolution — 2 already-resolved promises 856.6 K 0.0012 0.0031 ±1.05% +9.0%
promiseHash – concurrent resolution — 5 already-resolved promises 527.8 K 0.0019 0.0027 ±1.59% ~0%
promiseHash – concurrent resolution — 10 already-resolved promises 314.4 K 0.0032 0.0049 ±1.39% +6.9%
promiseHash – concurrent resolution — 5 promises with object values 491.8 K 0.0020 0.0027 ±0.29% +2.8%
timeout – wrapping fast promises — timeout wrapping an already-resolved promise (1 s budget) 945.8 K 0.0011 0.0017 ±0.39% +4.2%
timeout – wrapping fast promises — timeout wrapping an already-resolved object (5 s budget) 873.1 K 0.0011 0.0020 ±1.71% +3.7%
benchmarks/utils/safe-redirect.bench.ts
Suite — Benchmark ops/sec mean (µs) p99 (µs) ±rme vs main
safeRedirect – valid paths — root path / 6.98 M 0.0001 0.0002 ±0.18% +2.8%
safeRedirect – valid paths — simple path /dashboard 3.43 M 0.0003 0.0003 ±1.28% +2.0%
safeRedirect – valid paths — nested path /settings/profile 3.07 M 0.0003 0.0004 ±1.24% +2.3%
safeRedirect – valid paths — path with query string /search?q=hello 3.42 M 0.0003 0.0003 ±1.27% +2.7%
safeRedirect – valid paths — path with hash /docs#section 3.36 M 0.0003 0.0003 ±0.10% ~0%
safeRedirect – rejected inputs — absolute URL https://evil.com 4.63 M 0.0002 0.0003 ±0.12% +1.7%
safeRedirect – rejected inputs — protocol-relative URL //evil.com 4.71 M 0.0002 0.0002 ±0.14% +2.1%
safeRedirect – rejected inputs — backslash-relative /\evil.com 4.66 M 0.0002 0.0004 ±0.10% +2.4%
safeRedirect – rejected inputs — path traversal /../etc/passwd 4.11 M 0.0002 0.0003 ±0.10% +4.8%
safeRedirect – rejected inputs — null input 16.18 M 0.0001 0.0001 ±0.10% ~0%
safeRedirect – rejected inputs — undefined input 15.75 M 0.0001 0.0001 ±0.18% -2.2%
safeRedirect – rejected inputs — empty string 14.62 M 0.0001 0.0001 ±0.10% -9.0%
safeRedirect – percent-encoded bypass — percent-encoded // (%2F%2F) 4.60 M 0.0002 0.0003 ±0.10% +5.7%
safeRedirect – percent-encoded bypass — percent-encoded path traversal (%2e%2e) 4.89 M 0.0002 0.0002 ±0.09% +7.4%
safeRedirect – percent-encoded bypass — mixed percent-encoded absolute URL 4.60 M 0.0002 0.0002 ±0.09% +6.2%
safeRedirect – custom default redirect — valid path with custom default 3.66 M 0.0003 0.0003 ±0.26% +3.1%
safeRedirect – custom default redirect — invalid input with custom default 4.73 M 0.0002 0.0002 ±0.20% +2.3%

🔬 Compared against the latest benchmark run on main. ⚠️ = >10% slower · 🚀 = >10% faster.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: A-J Roos <asjasroos@pm.me>
Replace the root-extending tsconfig with a standalone bundler-oriented
config matching examples/starter/src/client/tsconfig.json exactly:
- module: ESNext + moduleResolution: Bundler (instead of NodeNext)
- target: ES2024, strict, noEmit, skipLibCheck, isolatedModules
- verbatimModuleSyntax: true
- types: ["vite/client"]

This avoids NodeNext-specific resolution issues in .tsx route files and
keeps editor/tsc behavior consistent across both examples.

Co-authored-by: Asjas <3828967+Asjas@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Asjas/scratchyjs/sessions/9b343b7d-1629-48a3-8d65-b8f076f8551a
Copilot stopped work on behalf of Asjas due to an error March 23, 2026 17:03
@Asjas Asjas merged commit 00bb8f8 into main Mar 23, 2026
16 of 17 checks passed
@Asjas Asjas deleted the copilot/create-streaming-ssr-starter-example branch March 23, 2026 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants