Codegea

Development

Custom software developmentSystems, platforms and integrations built around your operationCorporate websitesProfessional, fast and built to inspire trustOnline storesCatalog, payments and shipping ready to sell 24/7WooCommerceFast WooCommerce stores with each country’s payments and invoicingLanding pagesA page focused on one goal: turning visitors into customersMobile appsCustom iOS and Android apps that put your business in your customers’ pockets

Integration and CMS

CMS integrationsWordPress, Strapi, Directus and Arc XP connected to your whole ecosystemArc XPSupport, implementation and integrations for the media platformMigrationsMigrate your site, platform or database without losing anything

Growth and infrastructure

AI automationTechnology does the repetitive work and your team focuses on what mattersOn-page SEOBe found and recommended by Google: technical and content optimizationServers & hostingFast, secure servers monitored 24/7
RetainerYour dev team, by the monthFrom 20h a month. Fixed fee.
An AI quoting tool to quote services in minutesProfessional quotes in minutes, with projects, sellers and payments tracked in one place.See the case

By type

EcommerceStores and catalogsCorporate sitesBrand and lead genPlatforms & appsCustom software

Explore

All projectsCases and resultsClientsWho trusts usRetainerMonthly dev team

Blog

All articlesGuides and opinionNewsletterOne email a monthContactWe reply in 24h

Latest articles

How codegea.com is built: Next.js, Payload, Turso and R2CMS · 2026-10-20Cloudflare Workers: what they are and when to use themNode.js · 2026-10-19Live Preview in Payload with Next.jsCMS · 2026-10-19
NewsletterJoin our newsletterPractical web, SEO and AI notes.
ServicesCustom software developmentCorporate websitesOnline storesWooCommerceLanding pagesMobile appsCMS integrationsArc XPMigrationsAI automationOn-page SEOServers & hostingYour dev team, by the month
ProjectsProfessional quotes in minutes, with projects, sellers and payments tracked in one place.EcommerceCorporate sitesPlatforms & appsAll projectsClientsRetainer
Retainer
BlogAll articlesNewsletterContactHow codegea.com is built: Next.js, Payload, Turso and R2Cloudflare Workers: what they are and when to use themLive Preview in Payload with Next.jsJoin our newsletter
ESEN
Let's talk

CMS

How codegea.com is built: Next.js, Payload, Turso and R2

The real stack behind this site: Next.js and Payload in a single project, Turso as the database, Cloudflare R2 for images, and the problems we ran into setting it up.

Derian Pinto · Founder & Full Stack Developer2026-10-204 min

This site is not a template or a WordPress theme. It is a single Next.js application with Payload CMS inside it, a Turso database and images on Cloudflare R2. We built it this way because it is the way of working we offer clients, and we would rather tell you what actually happened than draw a pretty diagram.

One application

Next.js 16 with the App Router and incremental static regeneration for the public side, and Payload 3 installed in the same app: the admin lives at /admin and there is no separate CMS server. That means one repository, one deployment on Vercel and TypeScript types shared between content and pages. If we change a field in a collection, the compiler tells us where it breaks.

Queries don't go over HTTP. Pages use Payload's Local API, which is a function call inside the same process. It is faster and saves us from building an API that only we would consume.

Why we picked Payload over Strapi or Directus is in Payload CMS vs Strapi vs Directus in 2026, and the comparison with WordPress is in WordPress headless vs Payload CMS.

What goes in code and what in the CMS

Not everything is in the admin, and that is on purpose.

  • In code: the home page, the service pages, the retainer, contact and the legal texts. They rarely change and we want them to go through Git and review.
  • In Payload: the blog, projects, menus, site settings, forms and redirects. That is what gets edited often.

The site is in Spanish and English. URLs come from a single routes file and are never written by hand in components, because a malformed URL on a two-language site is noticed late and costs rankings.

The database: SQLite locally, Turso in production

In development we use a SQLite file. In production, Turso. The code is the same Payload adapter in both cases; only two environment variables change:

db: sqliteAdapter({
  client: {
    url: process.env.DATABASE_URL ?? 'file:./data/payload.db',
    authToken: process.env.DATABASE_AUTH_TOKEN,
  },
}),

DATABASE_URL points to file:./data/payload.db locally and to libsql://… in production. In development the adapter syncs the schema on its own; in production only migrations run, and the build script runs them before compiling: payload migrate && next build.

Turso is not the only option, nor always the best one. We cover it in more detail in the articles in this series about Turso and about which databases Payload supports.

Images: Cloudflare R2

Images are not stored on the server. Payload uploads them to an S3-compatible bucket and, in production, that bucket is Cloudflare R2 with a custom domain. We use the official S3 storage plugin and, locally, MinIO in a Docker container, which speaks the same protocol. For the code it makes no difference.

When a public domain is configured, the browser requests the image straight from the bucket and Payload stays out of it. Without that domain, which is what happens locally, Payload serves them itself.

Cloudflare: what we use and what we don't

To be clear: from Cloudflare we use DNS and R2. codegea.com does not go through Cloudflare's proxy and has no WAF rules turned on. Cloudflare as a firewall is another story that we have set up on client projects, and we will tell it separately.

Details we like

  • Live Preview: while editing an article you see the real result as you type, using Next.js Draft Mode.
  • Markdown version of every article: adding .md to the URL of a post or project returns the clean text, meant for agents and AI tools.
  • MCP: the CMS exposes an MCP server with API keys, so an assistant can read and create content without going through the admin.
  • Legacy redirects: the previous site had URLs indexed on Google. We kept them identical, and the ones that no longer exist answer 410 instead of redirecting to the home page, so Google forgets them without creating soft 404s. We go through that in how to migrate your website without losing Google rankings.

What went wrong

Some things didn't work the first time and are worth writing down.

  • disk I/O error when seeding data. The local SQLite file is shared between the dev server and the seed script. If the server is open and we seed the same database, it fails. We now verify with a separate database and build.
  • Uploads that went missing. When creating images from a script, the second upload in the process never reached the bucket. The S3 plugin keeps state in the request context; reusing it between calls left it in a bad state. The fix was to pass a fresh context on every upload.
  • Text that doesn't exist in one language. An article without a translation is not advertised in the other language, instead of showing text in the wrong one. It is less content, but it is correct content.

Is this for your project?

It depends. If your site is a blog that rarely changes, WordPress is still reasonable. If you have two languages, structured content, a team that uses TypeScript and you want a single deployment, this combination holds up. If you need a database with heavy concurrent writes, Turso is not the first option we would look at.

If you want something similar, see how we work on custom software development.

Share this article?LinkedInWhatsAppX

Written by

Derian Pinto

Founder & Full Stack Developer

Founder of Codegea. Builds custom websites, stores and platforms, and integrates and supports Arc XP for media companies.

Join our newsletter

An email with news and practical web, SEO and AI notes. No spam, unsubscribe anytime.

Shall we build your project?

Need help? Chat with us