Subfolio: AI Porting AREA 17's Filesystem CMS
A decade of real client work ran on software that died in 2017, with a security hole nobody was going to patch. I brought the engine back in three weeks, solo using Claude and DeepSeek. Same folders, no server, nothing left to patch.
- 35%
- Lossless WebP across a real 40-file gallery. Not one pixel changed.
- $21.83
- What free hosting for life actually cost: 3 weeks, Claude Pro, and DeepSeek.
- Priceless
- 859.2 KB and $1.83 to prove Fable or Opus can't beat me at my own game.
If you arrived here from a social link, the OpenGraph card is a still from AREA 17's case study on OpenAI. Their work, not mine, and not a screenshot of Subfolio. I picked it on purpose. AREA 17 built Subfolio, and their OpenAI archive still runs on it, but that archive is a file listing and it undersells the engine. I could've used the cover to the OpenAI brand guidelines but it's black and white and would look boring. I'm all about the hooks, never about the bait. This frame carries the intent of the project better than a picture of its output does. click!
Or skip the pitch and watch it work: archive.ilano.fyi is this port in action, serving my actual working folders.
What it is
Subfolio is a filesystem CMS: the folder is the website. No database. No admin screen. No server. Name your files, drop them in folders, and it builds the galleries, slideshows, and downloads. Renaming a file edits your site. How you name things is the whole authoring interface. A suffix makes a slideshow. A prefix hides a work in progress. Built by AREA 17, whose clients include OpenAI, ElevenLabs, and The New York Times. Their own archive still runs it, and their OpenAI work from 2023 is served by it right now.
Why I wanted it back
I wasn’t a bystander to it. I ran my own portfolio on Subfolio for years, and I used it on production work: the K2 Snowboarding 2012–13 boot line, and footage ingestion and selects for Red Bull’s Where the Trail Ends promotional BluRay/DVD book. When the job is getting large volumes of images and video in front of people fast, Subfolio is the right tool. The port exists because I wanted the workflow back.
WordPress? Squarespace? Framer? They’re undeniably great tools, but you can’t just dump a folder and have it render every folder, subfolder, and image from a copy and paste. Subfolio? The folder is the website. Elegant. You can update it without worrying. If it’s hard to update… you won’t update it.
Responsibilities
- Architecture and the static-build pivot
- Driving the LLM port — Gate and Wave: Opus on the Gate, DeepSeek on the Wave — reviewing every file
- Performance pass, budgets, honest measurement
Why it had to move
The stack died twice. PHP 5.6 reached end of life on 31 December 2018. Kohana, the framework under it, was retired on 1 July 2017, and its last stable release shipped in July 2016. Upgrading in place was never an option, because Kohana 2.x cannot run on PHP 8. Neither gets patched. Ever.
This was not theoretical. In 2016 someone used a PHP hack on my portfolio site. Dreamhost support told me it had dug its way in, that it was unrecoverable, and not to run it on their shared hosting again. The technical explanation is in the write-up.
TL;DR
Ported a dead PHP CMS to static Astro in three weeks, solo. The agent wrote code; I made every call that mattered. No server, $0 hosting, and 35.0% smaller images.
Knowing when to overrule a black box is the skill this port was actually testing.
The model wrote most of the code. It didn’t make one call that mattered. It spent days telling me I wasn’t qualified to decide a font. Fonts are my actual job. It hadn’t read the file that says what I do. That was the light bulb. It also kept demanding a Lighthouse score, for a static build on a CDN, against a PHP app that can’t even run, for a site I last ran before I think Lighthouse existed. The font was arithmetic: almost a megabyte against one small file. It argued anyway. That’s when I knew. The full light-bulb story is in the write-up.
What it cost
Solo. No budget. A $20/month Anthropic plan, stretched — not saved. The Wave ran by hand: the model wrote the shell script, I dropped to a shell and ran it myself, every time. Mechanical volume landed on DeepSeek; the frontier quota stayed on the Gate. The ledger proves the routing worked — it was never about savings. And because the proxy picks the backend per task, no vendor owns the workflow. That’s sovereignty, and people aren’t thinking hard enough about it. The fan-out mechanics and the ledger are in the docs.
What the Wave actually cost. The DeepSeek dashboard, June 30 through July 8, the nine days that produced 54 of the port’s 57 commits: 181 API requests, 10,692,344 tokens, $0.31. Extend the window to the last commit on July 26 and it adds 14 requests, 18,130 tokens, and a quarter of a cent. The mix is the story. 10.6 million of those tokens were input, and 94.8% of the input was cache hits: the Wave re-read the same repository on nearly every call, about 59,000 tokens of context per request, and DeepSeek billed the re-reads at $0.0036 per million tokens. Output, the part where a model writes something, was 43,397 tokens, 0.4% of the total. The Wave was a reader, not a writer. Charge the whole month’s subscription to the project, which overcounts, and the port’s entire model spend comes to $21.83: twenty dollars of Anthropic plan, $1.83 of DeepSeek at the API rate.
What the same tokens cost on Anthropic’s rate card. Identical token counts at Anthropic’s published API prices, the caching column giving Anthropic its full 90% cache-read discount:
| Anthropic model (per 1M tokens, in / out) | No caching | With prompt caching |
|---|---|---|
| Sonnet 5 ($2 / $10) | $21.73 | $3.56 |
| Opus 5 ($5 / $25) | $54.33 | $8.92 |
| Fable 5 ($10 / $50) | $108.66 | $17.83 |
Against the actual $0.31 that is a spread of 11x to 346x, and it understates: these token counts are DeepSeek’s, and third-party measurement has Anthropic’s current tokenizer producing roughly 30% more tokens for the same text.
Rate cards have dates on them. Those are the June and July 2026 rates, the ones on the invoice. On August 16, 2026, three days before this page updated, DeepSeek raised V4 API prices by roughly 50% to 1,100% depending on token type and hour, and the steepest hike landed on exactly the token this workflow used most: cache hits, up 511% off-peak and 1,122% at peak. That eight-day dashboard window repriced on the new card comes to about $0.67 off-peak and $1.33 at peak. The window’s bill quadruples at worst and the argument does not move. That is what the proxy is for. A rate card is a promise with a date on it; the routing layer is the part you own.
What the $20 plan was actually buying. Claude Pro meters a rolling five-hour window plus a weekly cap, one shared pool across every model, and Anthropic does not publish the cap in tokens. What it says instead is who the cap is for: when weekly limits arrived in August 2025, Anthropic’s stated reason was a small number of subscribers running Claude Code around the clock. Ten million tokens of Wave traffic is exactly the pattern that cap exists to stop. Route the Wave through the $20 plan and there is no bigger bill; there is a dark Gate, the week’s allowance burned on volume while the decision model has nowhere left to think. The alternatives were a $100 or $200 Max plan, or API billing at the rates above. Neither happened. The plan stretched because the volume left.
A frontier model read this page and concluded the savings were “eight cents.” The reviewer was Claude Fable 5, Anthropic’s most expensive self-serve model at $10 per million input tokens, the one the US government export-controlled for a few weeks this summer, and the session transcript says so 192 times. The actual bill was thirty-one cents; the rate card of the model doing the sneering is the $108.66 row above. Eight cents was never the point. The point is the dashboards: Anthropic’s usage page shows Anthropic consumption against Anthropic quota and will never print a DeepSeek line item, and anyone auditing one vendor’s report is auditing half a workflow. The repo’s ledger records both sides. That is the entire reason it exists. I could fake every receipt on this page. Doing the work was easier.
Three weeks of ignoring an LLM and trusting twenty years of intuition it kept telling me not to trust. The measurement on this page is in kilobytes and invoices. No Lighthouse score appears anywhere in this project, because I never measured one.
What this Astro port of Subfolio does for speed
AREA 17 still runs Subfolio on their own archive, and their 2023 OpenAI page is a folder of 40 screenshots. Every one of them is a PNG. I re-encoded all 40 to lossless WebP to find out what the port’s image step would do to a real folder rather than to my own test content.
The 40 files come to 656.9 KB today. As lossless WebP they come to 427.1 KB. That is 35 percent, and not one pixel changes.
| Encoding | Total | vs today | Cut | Lossless |
|---|---|---|---|---|
| PNG, as served today | 656.9 KB | yes | ||
| WebP lossless | 427.1 KB | 229.8 KB less | 35.0% | yes, verified |
| PNG re-encoded, full colour | 625.5 KB | 31.4 KB less | 4.8% | yes |
WebP lossless beat a re-compressed PNG on all 40 files, saving between 23 and 65 percent each, with a median of 32 percent.
The reason the format mattered more than the ratio is timing. iOS Safari did not support WebP until iOS 14 shipped in September 2020. By then mobile had been the dominant platform in the United States for years: mobile and tablet passed desktop worldwide in October 2016, the US crossover followed in Q1 2017, and by 2019 the US split was 54 percent mobile against 41 percent desktop. A format the majority platform could not read was not a format you could ship, however well it compressed.
An improvement is also not a recommendation. I measured their files, and I have no idea what they’re working with behind them. I don’t know their build, whether the archive is frozen on purpose, or whether anyone has a reason to touch it again. Only the people who maintain it can weigh 35 percent against the cost of changing anything, and they have context I don’t.
In my port the 35 percent is cheap to take, because the folder is the interface and the output is files. The thumbnails already get made by a step that already runs, so a WebP sibling is one more file out of it, served through a <picture> element with the PNG left in place as the fallback. I know that about my build because I wrote it.
Two holes, and why I deleted them instead of patching them
Anyone could ask the old engine for any file on the server. The address bar carried the path, and the app passed it along without checking whether it was a path a visitor should be allowed to ask for. In 2016 someone used that against my portfolio.
The passwords were barely locked. The account file accepted passwords in plain text. Passwords that did get scrambled went through a method built for speed, which is exactly backwards for storing a password, and every scrambled password on every install was locked with the same key. That key was not a secret. It was typed into the source code and published:
// engine/application/config/filebrowser.php
// NOTE IF YOU CHANGE THIS YOU WILL NEED TO REGENERATE YOUR HASHED PASSWORD LIST
$config['auth_salt'] = 'W8Kivk5ykGhSrc11';
Anyone can still read it on GitHub. The comment above it states the design intent out loud: one key for every user, and changing it locks everybody out at once.
The port does not patch either one. It takes away the conditions for them. The site is assembled before anyone visits, so there is no path left to hand the server and nothing to point at the wrong file. And there is no login screen at all, which is a deferral rather than a fix, and the docs say so. You cannot pick a lock that was never installed. The full security analysis is in the write-up.
What changed, and who decided it
| Change | Before | After | Decision? |
|---|---|---|---|
| The stack | A Go rewrite, already underway | Ported instead, static Astro build | Ryan Did |
| The engine | Rebuilt every page on every visit | Built once, served as plain files | Claude Did |
| The conventions | A decade of folder naming rules | Unchanged, byte for byte | Ryan Did |
| The security hole | Security theater | The code that allowed it no longer exists | Ryan Did |
| Gallery previews | PNG and JPEG only | WebP and AVIF, 35% smaller, originals untouched | Claude Did |
| Fonts | 906.5 KB, five formats | 47.3 KB, one variable font | Ryan Did |
| Hosting | A server to patch forever | Free, static, nothing to patch | Ryan Did |
The annotated version, with what each call cost and who it came from, is in the write-up.
Proof
- The story of the port, told as a narrative: Resurrecting Subfolio
- The archive, my 2015 portfolio served by this port, the exact 2015 site one folder down: archive.ilano.fyi
- Live demo, the port serving real content: ryanilano.github.io/subfolio-astro
- Documentation site: ryanilano.github.io/subfolio-astro-docs
- Engine source: github.com/ryanilano/subfolio-astro
- The Port Journey, the full engineering deep dive: why the port, architecture, the DeepSeek workflow, performance, security
- The original: github.com/area17/subfolio (AGPL-3.0)