Skip to content

LiveOctober to November 2026

Outlet Powered

Backup power for US homes, with every spec linked to the maker's own page or manual.

I built Outlet Powered to help US households choose a portable power station for outages. Every figure on a model page links to the maker's US product page or manual, with the date it was checked, and a figure the maker does not state reads "Not stated". Runtimes are calculations shown as ranges with the maths beside them, and the site says plainly that it does not test products. The content lives in Postgres behind an admin, pages can be scheduled to go live by themselves, and an AI guide answers questions from the same data.

US models live on 11 November 2026
33
maker pages and manuals linked (11 November 2026)
103
appliances with published wattage
54
brands
7
Vitest tests passing on 10 November 2026
1,137
database tables
15
scheduled cron entries
5
commits, 9 October to 10 November 2026
134

Architecture

Next.js 16 with Postgres content and tag-based caching

My role: Founder, editor and developer. I set the requirements, reviewed and tested the work, and review each page before it goes live, working with AI coding assistants.

  • seed/

    The content in Git: one JSON file per model, the appliances with their sources, and the pages in Markdown. It is also the fallback when the database cannot be read.

  • src/db

    A Drizzle schema of 15 tables, the seed loader, the deploy sync and the export that writes the database back to Git.

  • src/lib/data

    Cached readers for products, appliances, pages, settings and redirects, one cache tag each. Items with a future publish time are dropped here.

  • src/app/admin

    The admin: pages, products, appliances, settings, redirects, messages, the audit log, guide questions and images.

  • src/lib/runtime.ts

    The runtime and fit maths, shared by the calculators in the browser and the tables built ahead of time. Its assumptions are named constants.

  • src/lib/guide

    The AI guide: the Claude call, three read-only tools on the site's own maths, the limits and the answer check.

  • src/lib/jev

    Jev for the finder, contact triage and the admin's spec check, with request guards, a 24-hour cache and keyword rules as the fallback.

  • Next.js 16
  • React 19
  • TypeScript
  • Drizzle ORM
  • Neon Postgres
  • PGlite
  • Zod
  • unified (remark and rehype)
  • sharp
  • Vercel Blob
  • Vercel Cron
  • Cloudflare Turnstile
  • Claude Haiku 5.5
  • Jev (TypeSafe AI)
  • Vitest
  • Playwright
  • axe-core

The site

Seven pages from the live site

Taken from outletpowered.com on 11 November 2026. The numbers in them are the site's own data on that day.

Outlet Powered home page with the headline, a short note on how the site works and four counts: models, source links, appliances and brands (opens the full-size screenshot in a new tab)
Home: what the site does, who runs it, that it does not test products, and the counts drawn from its data.

Evidence

A sourced product database

A figure goes on the site only with a link to the maker's US product page or manual and the date it was checked.

The site covers portable power stations, solar generators and plug-in solar for US homes facing outages. It collects the makers' own figures, shows where each one came from and does the runtime arithmetic on the page. It does not test products. The site is built to be funded by affiliate links.

Where the figures come from

  • Each spec value is stored with its source and a note, and each model lists its source links with the date they were checked.
  • When the maker does not state a figure, the page says "Not stated". Gaps are never filled with an estimate or a figure from another model.
  • When a product page and the manual disagree, the manual wins and the page notes the difference.
  • Tests published by other outlets appear under "Measured by others", credited by name with the date and a link.

Rules the build enforces

  • A unit test fails the build on any claim of first-hand testing in the content and data files.
  • No prices are published until a store feed supplies a price with its timestamp.
  • Every store button reads "Check price at" and the links come from one data file.

Still open

  • Each model page says whether I have finished a second check of its figures against the printed manual. None is finished yet, and the About page says so.
  • Runtimes are calculations from rated capacity, not measurements, so the site shows them as ranges.

Content

Postgres content with the seed in Git

Pages, products, appliances, settings and redirects live in Neon Postgres and are edited in the admin. The same content ships in Git as the seed.

Saving a change

  • Every admin save is validated against the whole site, then stored with a revision and an audit row.
  • A save expires only the cache tags it touched, so the affected pages rebuild on their next visit without a redeploy.
  • Public pages are prerendered and served from the CDN, so a page view costs no database query.

Git and the database

  • Without a database the site renders from the bundled seed. If a database read fails, the seed is shown for five minutes only.
  • Deploys add new seed pages and products and refresh rows nobody has edited. A row saved in the admin is never overwritten by a deploy.
  • An export command writes the database back to the seed, so Git keeps a reviewable history of admin edits.

Images

  • Admin uploads are decoded by sharp, re-encoded to 640, 1024 and 1600 px WebP with metadata removed, and stored in Vercel Blob in a new folder each time.

Tools

Runtime calculator, appliance tables and comparisons

The calculators and the tables built ahead of time share one module of runtime maths, and its assumptions are named constants shown on the pages that use them.

Runtime calculator

  • Rated watt-hours times an efficiency range (80 to 90% on AC, 85 to 95% on DC), divided by the load, shown as a range.
  • For a chosen station it checks running watts against the continuous rating and start-up watts against the surge rating, and warns about DC-only stations.

Will it run

  • 54 appliances with published wattage, each tied to its source.
  • Refrigerator, freezer and CPAP pages each carry a runtime table for every model, and a size calculator works out the battery a plan needs.
  • CPAP figures use ResMed's own numbers: 56.1 W typical and 73.2 W peak for the AirSense 11 with a heated humidifier.

Database explorer and comparisons

  • A filterable card grid with a table view and a dialog that compares up to three models.
  • 12 comparison pages, brand against brand and model against model, with spec tables that highlight the rows that differ.

Recalls

  • A recall tracker built from searches of the CPSC recalls API, with the date each brand was checked.

AI

An AI guide whose answers are checked on the server

The "Ask the AI guide" box runs on Claude Haiku 5.5. It answers from a catalog context and three read-only tools on the site's own maths, and code checks every reply before a visitor sees it.

What the check holds back

  • A number that is not in the database, the tool results or the visitor's own message. Small counts pass, but a small number with a unit, such as "8 hours", needs a source like any other.
  • A price, a claim of testing, wording about wiring a station into a house, or a link to another website.
  • Markup, an email address, or talk about its own prompt or rules, which the model has no reason to write.
  • Links are kept only when they point at an existing page on the site.

Limits

  • Per visitor: 10 questions per 10 minutes and 30 a day. Site-wide: 1,000 answers a day, then the guide shows the contact form.
  • 500 characters a question, 6 turns, a 15-second timeout and 3 tool rounds.
  • Questions are kept for 30 days without the IP address and reviewed in the admin.

Describe your home

  • In the finder, a visitor can describe a home in a sentence. Jev, TypeSafe AI's System One model, decides which of the site's appliances were named, how long the outage is and whether medical equipment is involved. It writes no text.
  • Code does every calculation. Jev's answers count only for options the site offered: 0.7 adds an item and 0.45 suggests it.
  • After a 4-second timeout, or past the daily cap, keyword rules answer instead.

Publishing

Scheduled publishing in one push a week

A page or a model can carry a publish time and go live by itself, so a week of content ships in one push and one build.

How it works

  • A publishAt time in UTC on a page or a product file. The cached public readers drop anything whose time has not come.
  • A release cron runs at 13, 14, 18 and 19 UTC, the 9 AM and 2 PM Eastern slots in winter and summer. When something fell due, it expires the page and product caches and sends the new URLs to IndexNow.
  • The admin has a Scheduled status with a US Eastern time and refuses a time in the past.
  • Saves are validated with scheduled items counted as live, so a page cannot be saved if it would break once everything scheduled is out.

Checks

  • A unit test parses the seed at every scheduled instant, so no live page can point at a model that is not out yet.
  • An end-to-end test schedules a page, refuses a past time and publishes.

Daily job

  • A daily cron at 04:17 UTC cleans up and sends IndexNow every sitemap URL whose last change was today or yesterday.

Security

Spam defences, headers and the admin

Forms and paid calls

  • The contact form, the AI guide and the finder sit behind Cloudflare Turnstile. The widget is interaction-only, and a passed check sets a signed 10-minute cookie.
  • If Cloudflare cannot be reached, the contact form still takes the message, and the guide and finder fall back to the free answers, never the paid model.
  • The contact form has a honeypot field and a minimum fill time.
  • Messages that look like spam, such as many links, a throwaway email domain or text addressed to AI readers, are stored under a spam label. The rest are posted to a private Discord channel.

Headers

  • A Content-Security-Policy that defaults to the site's own origin and adds a host only for a service that is configured.
  • HSTS for two years, X-Frame-Options DENY, nosniff, a Referrer-Policy, a Permissions-Policy and a same-origin opener policy.

Admin

  • Passwords hashed with scrypt, optional TOTP two-factor sign-in and an HMAC-signed session cookie.
  • Every admin action checks the session on the server first.

Testing

One release gate before every push

There is no GitHub CI, by choice. One script is the release gate, and it runs before every push to the main branch.

The gate

  • npm run verify runs the contrast check, unit tests, the type check, the build, the writing check and the end-to-end suite on Chromium, plus WebKit in WSL.
  • A pass is tied to the exact tree it checked, and the pre-push hook skips the checks only when the pushed tree matches.

Tests

  • 1,137 Vitest tests in 25 files passed on 10 November 2026.
  • The end-to-end server uses a throwaway PGlite database, a test-only admin and a mocked Jev, and checks that an admin save shows on the public page straight away.
  • Accessibility is checked with axe in the end-to-end suite.

Writing rules

  • A checker fails the build on em dashes, invisible characters, testing claims and three one-sentence paragraphs in a row.

Growth

Technologies and skills used

Data modelling
Spec values stored with their source and check date, Zod schemas, and whole-site validation on every save.
Full-stack TypeScript
Next.js 16 with Cache Components, tag-based cache expiry, server actions and an admin.
Databases
A 15-table Postgres schema with Drizzle, revisions and audit rows, and a Git seed as the fallback.
AI engineering
A Claude guide with read-only tools and a server-side answer check, and Jev for typed judgement calls.
Publishing
Scheduled releases on fixed cron slots, cache expiry on release and IndexNow pings.
Security
Cloudflare Turnstile with a signed pass cookie, CSP and HSTS, spam rules and scrypt with TOTP.
Testing
Vitest, Playwright on Chromium and WebKit, axe checks and a single release gate.