# Kryštof Francl — francl.digital (full content)
> Websites, integrations and AI automation. An analyst who writes the spec and then builds it.
Self-rendered, single-document copy of every public page on https://francl.digital for LLM ingestion. Source of truth: same Astro content collections that drive the site.
## About
- **Name:** Kryštof Francl
- **Title:** IT Analyst · Systems Integration · AI Automation
- **Location:** Prague
- **Active since:** 2020
- **Email:** krystof@francl.digital · **LinkedIn:** /krystof-francl
### Primary skills
- Web development
- IT analysis
- Systems integration
- AI automation
- Requirements analysis
- Digital transformation
### Tools
- BitBucket
- GitHub
- Confluence
- JIRA
- VS Code
- Claude Code
- Photoshop
### Stack
- TypeScript
- Node.js
- Python
- LangChain
- MCP
- REST API
- UML
- Astro
- Cloudflare Workers
- Tailwind CSS
- Wrangler
- Biome
### Languages
- Czech (native speaker)
- English (professional working)
### Certifications
- Microsoft 365 Certified: Fundamentals
- Photoshop
- Advanced AI Applications
### Bio
I got my first computer at eight. My uncle had an electronics shop in Prague, so I had access to one before most of my classmates back in Horažďovice even knew what Windows was. I played games, but mostly I dug around the internet to see what I could find. That habit stuck.
At twelve I tried to mine Bitcoin. Downloaded the node, set up a wallet, then quit because I did not really understand what I was doing. Now I do. It does not bother me. I have always enjoyed watching things that look like science fiction right now and completely obvious in ten years. Gaussian splatting, vector databases, digital twins for small businesses. Stuff that sits quietly in the shadow of bigger noise. That is where I tend to look.
I have worked as an IT analyst for over six years. I translate between people who design processes and developers who write code. I take what the business side imagines and turn it into a specification that development can actually build from. Late last year I built a tool for my colleagues at Raiffeisenbank that took a chunk of manual work out of documenting API definitions — technically it is an MCP server for the AI assistant in the editor. Got a bonus for it. Now it is starting to connect with a project an external vendor is running there.
> That is what I enjoy most: making something that genuinely saves people work.
---
I have been in Scouts for eighteen years, at the Prácheň troop near Šumava. Not because I never got around to leaving. They are just my people. Prague is home, Horažďovice is base.
I have a degree. But I value what I learned by jumping into things before I knew how they would turn out far more than any title on paper. AI does not feel like a threat to me. It is a tool. And people who know how to use tools will always be needed.
## Services
### Web development
A website that brings you customers, not bills. Fast on a phone, cheap to run, no monthly fee for an admin panel — and you change the prices and text yourself.
- Company sites and presentations
- Internal tools and web apps
- Rebuilding an old site that no longer copes
- Launch, visitor numbers and a guide to editing it yourself
Indicative price: from €1,000 · Typical timeline: 2–6 weeks
Prices, opening hours, a new item on the list — you change those yourself, without waiting for me. The text is not locked inside an admin panel you pay for every month: it sits in plain files where you rewrite a line, save, and the site updates itself. The guide for doing that comes with the handover, and it is written for a person, not a programmer. Bigger changes — a new section, a different structure — are my job, but for the everyday edits you do not depend on me.
I build the site so that somebody else can take it over in two years: clean code, no closed platform, and no plugins that stop working after a year with nobody left to fix them.
Why it is quick: the page is finished in advance and the servers keep it ready close to the visitor, so nothing has to be assembled each time someone opens it. It appears almost instantly even on a weak signal, and hosting costs a couple of euros a month rather than hundreds. This site, the one you are reading, is built exactly that way.
## What you get
Design and delivery, not just code. We start with what the site has to do and who it is for, and the structure follows from that. As standard it works on a phone, can be operated with a keyboard and a screen reader, is findable in search (structured data, sitemap, hreflang for multilingual sites) — and you can see how many people come.
On handover everything is yours: the source code, the documentation, every access credential and the way the site gets published. None of it is tied to me — carry on yourself, hand it to somebody else, or keep me on for maintenance.
### IT analysis
Want something built, but the quotes you are getting differ by tens of thousands? I write the precise brief you can build from, compare quotes against, and use to tell when it is actually finished.
- Requirements gathering and refinement
- Process and data models (UML, BPMN)
- Functional specs and acceptance criteria
- Impact analysis before a production change
Indicative price: from €50 / h · Typical timeline: scope-dependent
I have spent six years as an analyst in banking. Most projects that stall do not stall on code — they stall because the person asking and the person building picture two different things behind the same sentence. My job is to find that gap before anyone pays for it.
## How it runs
I start with the people who actually operate the process, not only the person who commissioned the project. Out of that comes a model of the current state, a list of the real pain points, and a proposal for the target state. Only then do I write the specification — with diagrams, a data dictionary, and acceptance criteria that can be tested.
I can also work inside an existing team as the project's analyst. I know how change gets pushed through a corporate, and I will not hand you a proposal that dies in security review.
The analysis also stands on its own, with no development from me attached to it. The document you take out to vendors is the same thing that lets you compare what comes back — because everyone is finally pricing the same scope.
### Integrations and APIs
Your shop, stock and accounting finally talking to each other, so nobody retypes data by hand. Design, documentation, tests — and above all, clarity on what happens when one side goes down.
- REST API design and documentation
- Internal and vendor system integrations
- Data mapping and migrations
- Failure paths, retries and monitoring
Indicative price: from €50 / h · Typical timeline: 1–8 weeks
Integrations are where projects break most often. Two sides agree to "just send it over the API", nobody writes down what an empty field means, and three months later it is a production incident.
I work the other way round. The contract comes first — an OpenAPI definition, request and response examples, an explicit list of error states. Implementation follows. Both sides then look at the same thing, and testing can start before anything is finished.
## Operations are part of it
The design assumes the other side will be unavailable sometimes. Idempotency, retries, queueing and alerting are all in scope — so your partner's outage is not your system's outage.
Documentation is written alongside the interface, not after it. Whoever takes the integration over from me does not have to reverse-engineer it out of the code.
### AI automation
Does someone in your company retype numbers from e-mails and invoices into a system every week? A machine can take that over — wired into your real data, not a demo that only works on stage.
- Manual work with documents and e-mail taken off people
- Documentation and reports that write themselves
- An honest read on where AI pays off and where it is pointless
- Wiring into the programs you already run — safely
Indicative price: from €1,400 · Typical timeline: 2–8 weeks
Most of what people do inside a company is not thinking — it is moving figures from one place to another. Somebody retypes orders out of e-mails, somebody hunts through contracts for a clause, somebody assembles a Friday report out of five exports. That is the kind of brief I enjoy most: take something a person does by hand every week and hand it to a machine.
Last year I built one of those at Raiffeisenbank — it helps colleagues with API documentation work. It earned me a bonus, and it is now being wired into a project an external vendor runs there.
## First I ask whether it is worth it
Not every job wants AI. If the task has clear rules I will write an ordinary program — cheaper to run, and it never invents anything. AI belongs where the rules cannot be written down in advance: invoices that all look different, customer e-mails, contract text.
For the tool to be of any use it has to reach into the systems you already run. That is what MCP is for: a standard way to expose specific data and specific actions to a model, rather than the whole system.
When we do go ahead, I handle the boring part too: where the data flows, what gets logged, how output is verified, and what happens when the model gets it wrong. Without that, an AI tool is just risk with a nice demo.
## How an engagement works
1. **A call and a scope** — We sit down and go through what you need and why. I also talk to the people who will actually use it. Out of that comes a scope, a price and a date — not an estimate that triples in a month.
2. **Design and preparation** — I write down exactly what the thing has to do, where the data comes from and what happens when something fails. For a site that includes page structure and visual design. Before the first line of code, we both know what is being built — and how we will know it is finished.
3. **Build in slices** — I deliver continuously, not once at the end. You see the work in progress and can steer it while steering is still cheap — not at handover, when a change is expensive.
4. **Launch and hand-over** — I put it live, measure it, document it. You get the source code, every access credential and a guide to running and editing it — written for a person, not for a programmer. I can stay on for maintenance, but it is not compulsory.
- **The first call is free:** Thirty minutes on what you need. You leave with an opinion on whether it is worth doing and what it would take — even if you never hire me.
- **A price that does not move:** Scope first, number second. The price does not move unless the scope does — and if the scope moves, you hear about it before it reaches an invoice.
- **All of it is yours:** At handover you get everything: the site, the source code, the documentation and every access credential. There is no lock holding you here — carry on with me, with someone else, or on your own.
## FAQ
### What will it cost?
A website from €1,000 and AI automation from €1,400 — both quoted as a single total, not an hourly rate. IT analysis and connecting systems from €50 per hour, because their scope keeps being refined as the work goes. Either way these are entry points: the exact number comes after the first call and does not move from there unless the scope does.
### How long does it take?
A smaller site takes two to six weeks, connecting systems one to eight, an AI tool two to eight. I confirm the date after the first call rather than guessing in a proposal — and I deliver in slices, so you see a usable version well before the end.
### You work alone. What if you get ill or move on?
Yes, I work solo — which is why you talk directly to the person building it. The source code, the documentation and every credential are with you as the work happens, not only at the end, so anyone can pick the project up. On larger jobs I say upfront where I bring in a specialist.
### How does working together actually go?
A no-obligation call, then preparation and a fixed quote, then delivery in slices you can watch as it happens, then launch, documentation and hand-over. The full version is in the How I work section.
### Can you take over an unfinished or inherited project?
Yes, and it comes up more often than you would think. I start by going through what exists and writing down what is worth repairing and what is cheaper to rewrite. That analysis is billed separately, but it tells you the truth before you spend on development.
### How do invoicing and contracts work?
I trade as a Czech sole trader (reg. no. 08946043) and I am not registered for VAT, so the prices quoted are final. I invoice in CZK and bill larger engagements against milestones rather than in one lump at the end. Scope, price and date are in writing before work starts.
### Do you do maintenance afterwards? And does this work in English?
I do maintenance if you want it — either a retainer covering small changes and monitoring, or ad hoc as needed. It is not compulsory: turn it down and you pay me nothing monthly, and the site keeps running, because it is built so anyone can take it over. I run projects in Czech and English, usually remotely, and meeting in Prague is no problem.
## Education
- **VŠEM — University of Economics & Management, Prague** — Bachelor (Bc.) · Industry 4.0 (2022 — 2026) · in progress
- **Unicorn University** — Bachelor (Bc.) · Software Development (2020 — 2022)
- **Secondary Vocational School in Blatná** — Maturita (high-school diploma) · Informatics in Economics (2016 — 2020)
## Shipped websites
Live sites, verifiable by opening them. The relationship is stated for each — no client list is implied beyond it.
### Restaurace Shanghai
*Hospitality · built to commission* · [https://restaurace-shanghai.cz](https://restaurace-shanghai.cz)
A guest can browse the whole menu on a phone — 123 dishes across fifteen categories, filtered by category. Next to it the map and the number for reservations, with ordering through Foodora, so the restaurant has no new system to run.
A Chinese restaurant in Strakonice, running since 2004, needed one thing above all — for someone on a phone to reach the menu before giving up. It runs to 123 dishes across fifteen categories, so filtering and load time decide it, not effects.
The site is finished in advance and the servers keep it ready close to the visitor, so on a phone it appears in under a tenth of a second (measured from Prague). Running it costs a fraction of what a paid platform would. Ordering goes through Foodora, reservations by phone, the address sits on a map — nothing that forces the restaurant to operate another system.
Stack: Astro, Cloudflare, Static build
### Storage Energy Export
*Energy · family business* · [https://storageenergyexport.cz](https://storageenergyexport.cz)
A manufacturer of industrial battery storage needed a site that answers the first round of questions for him. A visitor walks through three capacity tiers from 500 kWh to 3 MWh, the use cases and the delivery process, and reaches the enquiry form already knowing what to ask for.
Battery storage does not sell off a web page — it sells off an enquiry someone sends once they understand whether it is for them. So the structure runs from the three capacity tiers (500 kWh–1 MWh, 1–2 MWh, 2–3 MWh) through the features and use cases to how a delivery actually works. The FAQ clears the questions that would otherwise be a phone call, and only then comes the form.
Technically it is the same approach as the rest — the site is finished in advance, appears in under a tenth of a second, and is set up so search engines find it in both languages. The company is my father's, which makes this the longest-running site I can check my own past decisions against.
Stack: Astro, Cloudflare, Structured data
## Work
### Integration Development Analyst — Raiffeisenbank Czech Republic (January 2025 — present)
*Banking · Prague* · [URL](https://francl.digital/work/raiffeisenbank/)
Replacing a bank's outdated system-to-system plumbing with a clean REST API, endpoint by endpoint. Plus AI tooling now used across the analysis department.
Bank systems have to talk to each other, and at Raiffeisenbank that job was handled for years by one ageing layer called TIF. TIF-Exit is the project replacing it with a clean `REST API`. Endpoint by endpoint, no big bang, because this is a bank and not a startup.
## About the project
The system we are replacing is an Oracle stack from a time when REST was still a buzzword at conferences. It did its job, but today nobody really knows why it works the way it works. Adding a new use case meant another workaround, a new colleague needed a month to find their footing, and every change waited on a two-week release.
The project is now in a workshop phase with all the systems connected to TIF. Every team has different priorities and a different idea of what "done" looks like. My job is to keep the whole transition together.
## What I actually do
I map existing integration flows and that is largely detective work. I read legacy code nobody remembers writing, then call people who were there eight years ago and ask why that unfortunate `boolean flag` is sitting there. The answer is always different but the point is the same: it was the fastest way at the time. Now it is technical debt holding the whole department hostage.
That turns into `REST` interface specs, `UML` and sequence diagrams. I coordinate development, integration and external vendors. Typical day: five people in a meeting, four opinions, one agreement needed by the end.
## RB as an AI lab
RB is one of the first banks to give employees access to modern AI models and tools, without unnecessary barriers or having to file a special request.
I made the most of it. I built several `MCP` tools to automate things that were slowing me down every day, from API definition documentation to syntax checking in specs. Then I showed the results to colleagues and managers, not as a presentation about what AI might one day do, but as a **real difference in hours of work that simply disappeared**.
Today most analysts in the integration department use those tools.
## What I learned
Migrating old systems is 80% about understanding and 20% about code. The best integration is the one nobody notices got replaced.
> When a company gives people access to good tools, what matters is who actually does something with that access. Showing a result works better than explaining the potential.
Tags: #Banking #Integration #REST #UML #TIF
### IT Analyst — Granton AI (September 2024 — January 2025)
*AI / Startup · Prague* · [URL](https://francl.digital/work/granton-ai/)
An AI assistant that files an accountant's invoices for them. I designed the document's path from scan to ledger entry and took accuracy from ~60% to ~90%.
**Billy** is an AI accounting assistant: you upload an invoice and the entry lands in the accounting system without anyone retyping it by hand. Simple thesis, complicated reality. We had four months to build the first usable version.
## About the product
The problem Billy solves is boring but real. Accountants spend a big part of their day copying data from invoices and statements into systems. It is not hard work, but there is a lot of it and it can be automated, at least 60-70% of it.
Technically it was a combination of `OCR` for document recognition, an AI layer for data extraction and categorisation, accounting logic for correct bookkeeping entries and integration into existing accounting systems. Each of those layers had a different pace, a different owner and different problems.
## What I actually did
I worked on several fronts at once. I designed the AI pipeline: how a scanned invoice runs through `OCR`, gets passed to an `LLM`, is checked against accounting rules and ends up as a record in the books. At the same time I was implementing accounting rules into the system, because an LLM on its own has no idea how Czech accounting legislation works.
On top of that I was responsible for the first version of the user UI, an overview of which documents are being processed, which are done and how they got recorded. And prompt optimisation for the AI layer was its own discipline. The difference between a badly and a well-written prompt was in practice the difference between 60% and 90% extraction accuracy.
## What was different from banking
Results were visible within a sprint, not a quarter. Every week brought a new problem that needed a spec written and handed off to dev within that same week. No two-week release trains, no waiting for sign-off.
It was a different kind of pressure. Better in that you can see progress. Harder in that a mistake in the spec shows up immediately.
## What I took from it
`LLM` is not magic, it is a component with edges. Small businesses have messier accounting than startups assume. And the most valuable analyst in an AI project is not the one who understands the models, but the one who can say where not to trust them blindly.
> Billy now works at multiple accounting firms in Czechia. That is a result I am not embarrassed by.
[granton.ai/billy](https://granton.ai/billy/)
Tags: #AI #Accounting #Startup #API #Data Flows
### Project Management Assistant — STORAGE ENERGY EXPORT s.r.o. (September 2022 — present)
*Energy · Prague* · [URL](https://francl.digital/work/storage-energy-asistent/)
My father's company. I run the Chinese supplier relationship for PV and battery storage, plan projects and help move the firm from biomass to renewables.
My father's company. Running in parallel with banking, different logic, different kind of responsibility.
## About the company
STORAGE ENERGY EXPORT is going through the kind of transition a company makes once a decade: from manufacturing biomass pelletising lines with robotics towards renewable energy — solar, hydroelectric and battery storage. Clients are primarily abroad, most of the business runs in English and across multiple time zones.
## What I actually do
My main focus is the relationship with the Chinese supplier of these technologies. Communication, keeping the partnership alive, negotiating terms and tracking deliveries. I went to China with my father in person — because in this kind of business, with this kind of partner, a long-term relationship does not get built from an inbox.
On top of that I run project planning: timelines, milestones, coordination between suppliers and my father. My job is to know where every project stands and to flag early when something is slipping or a conflict is coming.
## What I took from it
In a family firm decisions get made fast and the responsibility for them does not go anywhere. There is no "the boss approved it, I just executed". In a small team every decision has a name on it.
> It taught me to talk to people outside IT: suppliers, clients, partners from different cultures. To say things plainly, because there is nobody standing behind you to translate.
Tags: #Renewables #Project Management #B2B #International Trade
### IT Analyst — MONETA Money Bank (May 2022 — August 2024)
*Banking · Prague* · [URL](https://francl.digital/work/moneta/)
Two years on the app branch bankers use to open accounts and approve loans. Workflow design and integrations across almost every system in the bank.
Two years on the internal application that branch bankers use all day. The customer never sees it, but without it the business stops.
## About the app
The app runs the entire branch operation: opening accounts, approving loans, verifying client identity, blocking cards, signing contracts. A banker cannot move without it. At the same time it is software that never gets a marketing budget and customers have no idea it exists. It is still one of the most critical systems in the bank.
## What I actually did
The app runs on an internal `UFO Framework` — a low-code platform where workflows are designed using `UML` elements. Every tool I encountered at MONETA was either internal or something I had never seen before in my life. I learned all of them from scratch.
Over those two years I touched almost every system in the bank. Mortgages, `KYC`, credit cards, current and savings accounts, power of attorney, investment platforms. I designed workflows, specified `API` integrations between systems and figured out how data gets from one end of the bank to the other. I was not an analyst who owns one piece — I was an analyst on the application that connected all those pieces together.
## What I took from it
In a bank the most valuable work is invisible. **A good deploy is silent.** When a banker opens the app in the morning and everything works, nobody cares — and that is the point. When it does not work, the effect compounds: nervous client, nervous banker, nervous management.
I came to understand incident management as a job in two halves: technical diagnosis and keeping the room calm. Both matter equally.
> Understanding a system nobody wrote in one piece and nobody quite remembers is a more valuable skill than knowing how to write one from scratch.
Tags: #Banking #UML #API #Incident Management #Internal Apps
### Junior Consultant — manica s.r.o. (November 2021 — April 2022)
*Consulting · Prague* · [URL](https://francl.digital/work/manica/)
Finding where office tooling got in the way, building something better, and training the people who had to live with it. SharePoint and Microsoft 365.
manica was my first taste of consulting from the other side — not the one hiring, but the one being hired.
I did two things:
- **Analysis and implementation** — sitting down at the client, finding where their office tooling was getting in the way, and building something that made it better (usually around SharePoint and Office 365).
- **Training** — teaching people how the new version of their day worked. It very quickly taught me to stop saying "but it's obviously done this way".
I learned that the prettiest solution is useless if people either cannot or will not use it.
Tags: #SharePoint #Office 365 #Consulting #Training
### IT Administrator — UNIQA CZ (January 2021 — October 2021)
*Insurance · Prague* · [URL](https://francl.digital/work/uniqa/)
Merging the IT of two insurers after AXA + UNIQA. I was one of the bridges between the two estates — and kept the day-to-day running while it happened.
I walked into UNIQA right into a merger — AXA and UNIQA were combining and both sides needed someone who had seen the other half. My previous role at AXA made that someone me for a while.
## What an IT merger actually involves
On paper merging two large companies looks like a financial operation. In IT it looks like a decade of decisions from both sides overlapping — duplicate systems, overlapping tools, different access policies, different security standards. Most days on that kind of project are small adjustments: who gets access to what, which account to move, which system to keep, which to shut down.
## The day-to-day
- **Keeping systems running and up-to-date** — classic run-the-bank stuff. Updates, monitoring, oversight.
- **Working with other departments** — HR, compliance, business. Each one had a different idea of what "merger done" meant.
- **Putting out whatever was on fire** — things broke without warning. Figuring out why and restoring service was a daily routine, not an exception.
## What I took from it
Mergers are not pretty, but they are a great school of **patience**. And a school of the fact that in IT, change is accepted at the pace people accept it — not at the pace the technology allows. Decisions about which system stays and which goes were often political, not technical. I learned to **understand the motivations on both sides** before I started troubleshooting systems.
Tags: #IT Administration #Mergers #Infrastructure
### Junior IT Administrator — AXA (October 2020 — January 2021)
*Insurance · Prague* · [URL](https://francl.digital/work/axa/)
Preparing the infrastructure for a migration that later became the AXA + UNIQA merger, plus day-to-day operations at a large insurer. My first real IT job.
When two large insurers merge, IT feels it first: two systems that were never built to meet each other suddenly have to behave like one. AXA was where I met that kind of work for the first time — keeping things running, reacting when something went down, and fixing it quickly.
The main piece of work that stuck was **preparing the IT infrastructure for a migration** ahead of a merger with another company. That meant sitting down with both systems, understanding them, and finding the places they would either meet or fight.
The migration itself went without too much drama — which in this kind of project is the highest praise you can give it.
Tags: #IT Administration #Migration #Infrastructure
### Junior System Administrator — STORAGE ENERGY EXPORT s.r.o. (April 2020 — August 2020)
*Energy · Hejná* · [URL](https://francl.digital/work/storage-energy-admin/)
The family firm needed a network that would carry both daily operations and growth. I designed it, built it and ran it — cables, switches and access rules.
My first real IT project at home at the firm — building an internal network that could hold both day-to-day operations and future growth.
It came in three stages:
- **Design** — laying out the equipment, protocols, how it all hangs together
- **Build** — physical install, configuration, access rules
- **Operations** — monitoring, reacting to incidents, documenting it for next time
A few things I would do differently today. But the network is still running, so I guess those few things are not the critical ones.
Tags: #Networking #Infrastructure #IT Operations
### Webmaster — STORAGE ENERGY EXPORT s.r.o. (May 2017 — April 2020)
*Energy · Hejná* · [URL](https://francl.digital/work/storage-energy-webmaster/)
My oldest work still online — the family firm's website, from design through content to SEO. Not always beautiful, but it is still running.
The family firm's website was my first actual job. I did not have much experience, but I had time and the nerve to try.
Two parts to the work:
- **Design and build** — making something that looked decent, but more importantly, told people in the solar industry what the firm actually does
- **Content and SEO** — regular updates and some light work on search so people could find it
Looking back, it is my oldest piece of work still online, and I can see every beginner mistake I spent years unlearning. I am still quietly fond of it.
Tags: #Web Development #SEO #Content
## Blog
### Why I built this site
*20 April 2026 · 4 min read* · [URL](https://francl.digital/blog/why-this-site/)
> I could have stopped at a LinkedIn profile. Here is why I built my own site on Cloudflare Workers instead.
LinkedIn lends me a profile until Microsoft decides otherwise. I do not own the typography, I cannot decide what counts as a blog post and what counts as a CV entry. I am somewhere between a few hundred million people and ads for product management courses.
Your own site is different. You write the rules. Even if almost nobody notices.
## I wanted to try Workers
Edge computing interests me and I wanted to try Cloudflare Workers on something real. Zero cold start, a global network without configuration, you pay per invocation not per uptime. The best way to understand a platform is to build something on it.
It ended up as Astro 6 with the Workers adapter, Tailwind v4 with custom tokens, Biome as the linter, and the whole thing in two languages. Fourteen pages, one deploy command.
## I wanted to practice writing
Analysts write documents every day, but always to a template and for people you already know. Writing for an unknown reader on a topic you picked yourself is a different task. I want to get better at it.
So there will be occasional notes on things I am working through — banking integrations, LLMs in accounting, renewables, the odd tooling thing. No plan, no schedule.
## And I enjoy it
That is probably the main reason. Picking a typeface for the seventh time. Tuning the grid colour until it stops fighting the text. Figuring out why the font behaves differently than it should.
**A hobby.** And a hobby gets a pass on the things that do not quite make sense.
Welcome. If something reads poorly, drop me a line — I will probably agree.
Tags: #meta #astro #cloudflare #portfolio
### Raveo: how I built my own Cloudflare stack for client websites
*12 March 2026 · 12 min read* · [URL](https://francl.digital/blog/raveo/)
> Every commissioned website started from zero. So I built myself a foundation — the client manages the content, there is no server to maintain, and the site runs fast worldwide.
The problem with building websites on commission is that you start from scratch every time. Pick a CMS, set up hosting, figure out how to get content to the frontend, sort out caching. Then do the same thing again next time, just slightly differently.
I wanted my own foundation: a monorepo with CMS and frontend, deployable with a single command, with content the client manages themselves. No traditional server. That is how Raveo came together.
For whoever is paying for the site, that comes down to three things: they change the text and photos themselves instead of emailing a developer about a typo, there is no server anyone has to watch and patch, and the bill follows real traffic rather than a machine idling all month. The rest of this note is how it is put together underneath.
## What Raveo is
Raveo is a monorepo with two Cloudflare Workers. One runs `PayloadCMS` on `Next.js` as a headless CMS. The other runs an `Astro` frontend. Everything else is Cloudflare services: `D1` as the database, `R2` for media, `KV` for cache.
```
raveo/
├── apps/
│ ├── cms/ PayloadCMS + Next.js → Cloudflare Worker
│ └── web/ Astro v6 → Cloudflare Worker
└── packages/
├── types/ generated types from PayloadCMS schema
├── ui/ shared components + Lexical renderer
└── config/ shared tsconfig + Biome configuration
```
`Turborepo` manages the monorepo with a `pnpm` workspace. Deploying both workers at once takes one command.
## Cloudflare primitives: what each piece does
Before the implementation, a quick explanation of what each service actually is — it is not obvious from the names.
**Workers** are V8 isolates — small isolated environments that run code on each request. They do not start as a traditional server sitting in memory waiting. They spin up on demand, handle the request and disappear. No cold start, global network, you pay per real request not per uptime.
**D1** is serverless SQLite directly on the Cloudflare edge. No PostgreSQL server, no managed database. A SQLite file replicated globally, accessible via a Workers binding. For a CMS database that does not need millions of concurrent writes per second, it is a good fit.
**R2** is object storage compatible with the S3 API. Media files, images, documents. No egress fees for downloads — a meaningful difference from standard S3 when traffic picks up.
**KV** is a distributed key-value store with very low read latency. Good for caching: write once, read many times. With TTL expiration built in.
## PayloadCMS on Workers: D1 and R2 adapters
`PayloadCMS` normally expects a Node.js server and PostgreSQL or MongoDB. Cloudflare Workers are a runtime without Node.js and without TCP access to external databases. Adapters solve this.
In `payload.config.ts` the configuration looks like this:
```ts
export default buildConfig({
collections: [Users, Media, Categories, Posts, Pages, Forms, FormSubmissions],
globals: [Navigation, SiteSettings],
editor: lexicalEditor(),
// D1 instead of PostgreSQL
db: sqliteD1Adapter({
binding: cloudflare.env.D1,
push: !isSeed,
}),
// R2 instead of local filesystem or S3
plugins: [
r2Storage({
bucket: cloudflare.env.R2,
collections: { media: true },
}),
],
});
```
The `D1` binding is direct database access without a network hop. The `R2` binding is direct object storage access. Everything goes through internal Cloudflare channels, not the public internet.
The result: the `PayloadCMS` admin interface runs as a Worker. The client logs in, manages content, saves changes. Database is `D1`, media goes into `R2`.
## Service Bindings: how the workers talk to each other
Two workers need to communicate. The naive solution is calling the CMS over HTTP, but that adds latency, a DNS lookup and an extra network hop.
Cloudflare has Service Bindings for this. A direct connection between Workers without HTTP, without DNS, with zero latency. Worker A calls Worker B like a function, not an HTTP endpoint.
In the middleware it looks like this:
```ts
async function fetchFromCMS(fetcher: Fetcher | null, cmsUrl: string, path: string) {
const url = `https://cms${path}`;
const fallbackUrl = `${cmsUrl}${path}`;
const res = fetcher
? await fetcher.fetch(url) // production: service binding, zero latency
: await fetch(fallbackUrl); // dev: HTTP to localhost:3000
if (!res.ok) return null;
return await res.json();
}
```
`fetcher` is the Service Binding — available in production via `env.CMS`. In local development it is not available, so it falls back to HTTP. Switching is automatic.
## Middleware and KV cache
Astro middleware runs on every request. Its job is to load data from the CMS and pass it to pages via `locals`. Calling the CMS on every request would be unnecessarily slow.
So there is a `KV` cache in between:
```ts
async function cachedFetch(fetcher, cmsUrl, path, cache) {
// 1. Try KV cache
if (cache) {
const cached = await cache.get(path, 'json');
if (cached) return cached;
}
// 2. Cache miss — call CMS
const data = await fetchFromCMS(fetcher, cmsUrl, path);
// 3. Write to KV for 5 minutes (fire-and-forget, does not block response)
if (data && cache) {
cache.put(path, JSON.stringify(data), { expirationTtl: 300 }).catch(() => {});
}
return data;
}
```
The key detail is the `fire-and-forget` KV write. The response to the user does not wait for the cache write to complete. The write happens in the background.
On every request four things are fetched in parallel:
```ts
const [navigation, siteSettings, pagesData, postsData] = await Promise.all([
cachedFetch(fetcher, cmsUrl, '/api/globals/navigation?depth=1', cache),
cachedFetch(fetcher, cmsUrl, '/api/globals/site-settings?depth=1', cache),
cachedFetch(fetcher, cmsUrl, '/api/pages?depth=2&limit=100&where[status][equals]=published', cache),
cachedFetch(fetcher, cmsUrl, '/api/posts?depth=2&limit=100&where[status][equals]=published', cache),
]);
```
`Promise.all` matters here — all four calls go in parallel, not in sequence.
## Content revalidation
The KV cache has a five-minute TTL. But what if the client saves a change and wants to see it immediately?
Every collection in the CMS has an `afterChange` hook. When content is saved, the hook calls the Web Worker and tells it to invalidate the cache:
```ts
const revalidate = async () => {
if (process.env.NODE_ENV === 'production') {
// Service binding: CMS Worker calls Web Worker directly
await cfEnv.WEB.fetch(
new Request('https://web/api/revalidate', {
method: 'POST',
headers: { 'x-revalidate-secret': cfEnv.REVALIDATE_SECRET ?? '' },
}),
);
} else {
await fetch(`${webUrl}/api/revalidate`, { method: 'POST', ... });
}
};
export const revalidateAfterChange: CollectionAfterChangeHook = async ({ doc }) => {
await revalidate();
return doc;
};
```
After revalidation the KV cache expires and the next request fetches fresh data from the CMS. No rebuild pipeline, no waiting on a deploy.
## Lexical renderer: JSON to HTML without JavaScript
`PayloadCMS` stores rich text in `Lexical JSON` format — a tree of nodes. Ready-made libraries handle this through React and client-side JavaScript. But Astro pages are static and I do not want client-side JavaScript for this. So I wrote a custom server-side renderer.
The interesting part is text formatting. Lexical stores format as a bitmask — a number where each bit represents a different style:
```ts
function renderTextFormat(text: string, format: number): string {
let result = escapeHtml(text);
if (format & 16) result = `${result}`; // inline code
if (format & 1) result = `${result}`; // bold
if (format & 2) result = `${result}`; // italic
if (format & 8) result = `${result}`; // underline
if (format & 4) result = `${result}`; // strikethrough
if (format & 32) result = `${result}`; // subscript
if (format & 64) result = `${result}`; // superscript
}
```
The bitwise AND (`&`) checks whether a given bit is set. Text can be bold and italic at the same time — both bits are set simultaneously. The renderer walks the full tree recursively: paragraphs, headings, blockquotes, lists, checkboxes, links, images. Output is clean HTML with no client-side JavaScript.
## Rate limiting on KV
I did not want to bring in an external service for rate limiting. The `KV` namespace for cache is already there, so I used it for rate limiting too.
The implementation is a sliding window: for each IP address it stores a request count and the start of the current time window:
```ts
interface RateLimitEntry {
count: number;
windowStart: number;
}
```
Key design decision: the rate limiter **fails open** — if `KV` is unavailable, the request goes through. The alternative would be to reject the request, but a KV outage would then take down the entire site. Fail open is the right call here.
POST requests are limited to 10 per minute, API endpoints to 30. Responses include RFC-standard `RateLimit-Remaining` and `Retry-After` headers.
## Where it is now and where it is going
I am building the website for my Scout troop Prácheň on Raveo. It will be the first real production deployment of the stack.
Down the road I am interested in integrating `Medusa.js` as a transactional backend. That would make Raveo a base for e-commerce builds on commission: `PayloadCMS` for content management, `Medusa` for products and orders, `Astro` for the frontend. All without a traditional server, globally distributed, with content the client manages themselves.
The full project is open source at [github.com/raveo-dev/raveo](https://github.com/raveo-dev/raveo).
Tags: #Cloudflare #Astro #PayloadCMS #Workers #edge
### An MCP tool that catches errors in integration specs
*8 January 2026 · 7 min read* · [URL](https://francl.digital/blog/esmm-validator/)
> A typo in a spec surfaces at the tester or in the developer's code, and that means going back and redoing the work. So I built a tool that checks it earlier — an MCP server for the AI assistant in the editor.
Before a bank can connect two systems, somebody has to write down which field in one message corresponds to which field in the other. We call that document an ESMM and it is a spreadsheet. When someone mistypes a line in it, nothing on the page looks wrong: the error turns up either in test analysis or in the developer's code. Either way you go back to the spec and redo it.
It happened regularly and there was no tool that checked the entry beforehand. So I wrote one.
## What ESMM is and where the errors come from
Into the ESMM an analyst writes how data moves between systems: what `xpath` a field has in the input message, what it is called in the target, what conditions apply. From that come the technical specs, unit tests and implementation code.
Example `xpath` for a REST service:
```
createInternalPO//POST/request/BODY/accountNumber/numberPart1
```
And for TIF/WMB:
```
//getListRequest/identity/userLoginName
```
The format is different for each technology. REST has a different structure than TIF, arrays are written differently, slash rules differ. All of it has to match exactly against the `XSD` or `YAML` file structure. A typo, wrong capitalisation, an extra leading slash. None of these errors are visible in Excel until someone else starts processing it.
The rules are clear and mechanical. They can be checked automatically.
## What MCP is and what you can build with it
Before the implementation, a quick word on MCP as a concept, because it gets talked about a lot but rarely explained concretely.
MCP (Model Context Protocol) is a protocol that defines how an AI client (VS Code Copilot, Claude Desktop, ...) communicates with an external server. The server runs locally or somewhere on the network, the client connects, and from that point the AI has access to whatever the server exposes.
A server can offer four types of things:
**Tools** are functions the AI can call. In my case there are six: workspace discovery, temp folder creation, `XLSX` to markdown conversion, orchestration analysis, IMS file lookup, and validation input preparation. The AI gets a list of available tools with descriptions and decides itself when and how to call them.
**Resources** are static data or knowledge the AI can read. Mine are validation rules: a full spec of what valid `xpath` looks like for REST, what for TIF, what counts as a comment, what gets checked and what does not. The AI loads them as context before it starts validating.
**Prompts** are predefined templates that describe how to start working. Instead of the user writing an instruction from scratch, they pick a prompt and the server prepares a structured input. My workflow prompt looks like this:
```
You are an AI agent for complete ESMM validation. Run steps 1–8 sequentially.
Do not skip, do not parallelize.
1. discover_workspace_structure – find the service, save SERVICE_ROOT_PATH.
2. create_temp_validation – create a folder for artifacts.
3. convert_to_md – convert XLSX, head.md must be created.
4. analyze_orchestration_info – read orchestration, find services for IMS lookup.
5. find_ims_service – find files for each service.
6. create_ai_validation_sampling – build sampling_input.md.
7. auto_ai_validation – run sampling, save result.
8. Final report – summarise results or error.
```
The user writes a service name, triggers the prompt and the server takes them through the whole workflow automatically.
**Sampling** is the fourth type and the most interesting one. The server can ask the client through the protocol to call an AI model on its behalf. More on that below.
In `index.js` the initialisation looks like this:
```js
this.server = new Server(
{ name: "esmm-validation-server", version: "1.0.0" },
{
capabilities: {
tools: {},
resources: {},
prompts: {},
sampling: { createMessage: true },
},
}
);
```
Four lines, four capability types. The server communicates over `stdio` — the client starts it as a subprocess and everything goes through standard input and output.
## How it works step by step
The first six steps are a preparation pipeline. The server walks the repository structure and finds the service folder. It creates a `temp_validation` folder for intermediate results. It converts `XLSX` files to markdown, each sheet as a separate table. From the header sheet it reads which services are orchestrated and what technology they use. For each service it finds the corresponding files.
File lookup was an interesting problem because repository structures are not always consistent. I implemented multi-stage searching: first an exact match by folder name, then pattern matching by system prefix, then a recursive search of the whole folder. Only then does it say nothing was found.
After finding all files it builds `sampling_input.md`: one large markdown document with all data combined. ESMM tables, `XSD` structures, `YAML` definitions.
## MCP Sampling in practice
The seventh step is the key one. Instead of calling an AI API directly I used Sampling: the server asks the client to call the model on its behalf.
Why do it this way? The client (Copilot) handles authentication, rate limiting and model selection itself. The server does not need to deal with any of that. The model also sees the full working context in the client, not just an isolated API request.
Large ESMM files had to be split into blocks, otherwise they would not fit in the token limit:
```js
const MAX_BLOCK = 40000;
const blocks = [];
for (let i = 0; i < samplingContent.length; i += MAX_BLOCK) {
blocks.push(samplingContent.substring(i, i + MAX_BLOCK));
}
const messages = blocks.map((block, idx) => ({
role: "user",
content: {
type: "text",
text: idx === 0 ? block : `# CONTINUATION\n${block}`,
},
}));
const samplingResponse = await server.createMessage({
messages,
systemPrompt,
maxTokens: 16000,
modelPreferences: { intelligencePriority: 0.9 },
});
```
Each block is a separate message, the second and later ones have a `# CONTINUATION` header so the AI knows it is getting sequential data. The system prompt defines the exact validation rules.
The output is a markdown table:
```
| index | file | sheet | row | column | raw_path | error_code | expected |
```
If the AI returns something unusable or an empty response, the tool discards the result and returns an empty skeleton. Bad results do not propagate further.
## What does not work and what I am fixing
It would not be fair to only write about what works.
The biggest practical problem is the context window. With a larger number of files or more complex `XSD` structures the context gets exhausted before the AI finishes validating. Sampling breaks off or returns an incomplete result. I handle it by splitting into smaller blocks, but with really large ESMM files it still hits the limit.
Second issue: eight sequential steps the user has to trigger manually is inconvenient in practice. I built a simpler three-step workflow that compresses the whole process and the user triggers it once. Same result, much less friction.
Third thing, a positive discovery this time: the model matters a lot. `claude-sonnet-4-6` with Thinking mode handles things where other models get stuck. Specifically: when the tool cannot find a file due to a naming inconsistency, the model identifies where the problem is, fills in the missing input and continues into the sampling phase without the user having to step in. That is a difference that saves real time.
## Where it is now
Colleagues in the integration department are starting to use it. They run the validation before the ESMM goes to test analysis, and errors that used to surface at the tester or the developer get caught there instead.
The project is internal and still evolving. But the principle transfers: take a spreadsheet spec, compare it against the actual files, return what does not match. MCP gives that a clean structure: tools for actions, resources for knowledge, prompts for workflow, sampling for AI. Four building blocks and from them you can put together something that behaves like a proper participant in the development environment, not an isolated script.
Tags: #MCP #integration #validation #AI #banking