Nuxt on the Edge with Cloudflare Workers

I stepped out of Vercel's comfort zone into Cloudflare's edge ecosystem. Here's what I learned about deploying Nuxt apps on Cloudflare Workers.
Nuxt on the Edge with Cloudflare Workers

I've been comfortably settled in Vercel's ecosystem, enjoying their excellent DX and integrations. But lately, Cloudflare's expanding ecosystem caught my attention. The breadth and diversity of their products was impressive. Edge computing, databases, queues, AI models, vector stores, and more—all under one roof. Curiosity got the better of me and I decided to migrate one of my side projects from Vercel to Cloudflare.

What followed was an unexpected journey through edge computing constraints, database decisions, and valuable lessons in modern web architecture. This blog post documents this journey, sharing the solutions and lessons learned along the way.

If you're looking for a streamlined way to deploy Nuxt on Cloudflare, NuxtHub is the official solution that eliminates most of the complexity I'm about to describe. I chose the DIY approach specifically to understand the underlying mechanics and gain complete control over my infrastructure. And I'll admit, I borrowed plenty from NuxtHub's implementation.

Cloudflare Architecture

Workers vs. Pages

When deploying a Nuxt application to Cloudflare, you face an immediate choice: Cloudflare Pages or Cloudflare Workers? Both Nuxt and Nitro's documentation recommend Pages as the default option, which led me down that path. The Pages UI also looked more streamlined. So I assumed it would provide a Vercel-like one-click deploy experience.

Nothing could be further from the truth. While both run on Cloudflare's global edge network, they serve different purposes. Here's a quick comparison on some key capabilities:

CapabilityWorkersPages
Logs
Cron Triggers
Source Maps
Email Workers
Queue Consumers
Static Assets✅ (Beta, since Sept 2024)
Git Integration✅ (Beta)
Preview Deployments✅ (Beta, only preview URLs)

The coexistence of Workers and Pages has been a significant source of confusion within the Cloudflare community. As I understand it, historically, Workers was Cloudflare's native solution for running backend code on the edge, while Pages was introduced later with built-in static assets support to support full-stack deployments. However, this distinction has blurred now that Workers also supports static assets. Cloudflare itself acknowledges this in their documentation, indicating they "plan to bridge the gaps between Workers and Pages and provide ways to migrate your Pages projects to Workers." This indicates that Workers is becoming the preferred platform for all types of applications, while Pages may continue as a simpler entry point for static-first projects.

For Nuxt applications specifically, despite what the official docs currently recommend, I think Workers is now clearly the better choice for almost all Nuxt applications.

Cloudflare integration page for Nuxt does not mention Workers at all. Instead it recommends NuxtHub to build "full-stack Nuxt applications with Cloudflare". This was also a bit confusing for me while I was looking for a Workers-specific guide.

Edge Computing

Edge computing is one of those terms that sounds more complicated than it is. The idea is that instead of your code running in some distant data center, it runs... well, everywhere. Your application gets distributed across hundreds of locations worldwide, sitting as close as possible to your users. Think of it as the difference between ordering from a central warehouse versus picking up from your local store.

Cloudflare takes this concept to the extreme with their famous "deploy to region: earth" approach. Their edge network spans over 300 data centers globally, and when you deploy a Worker, your code instantly becomes available on all of them.

However, these edge nodes aren't running your typical Node.js runtime. They're optimized for running lightweight JavaScript/TypeScript code with specific constraints. This is where I hit my first roadblock - npm packages that worked perfectly fine in Node.js started throwing errors in the edge runtime.

Deploying on the edge means you always need to verify npm package compatibility with edge runtimes before adding them to your project.

Wrangler & Bindings

To deploy your Worker to Cloudflare or run it locally, you'll need the Wrangler CLI. It is your command-line Swiss Army Knife for managing everything Cloudflare related. At the core of Wrangler is the wrangler.toml (or wrangler.json) file, which is the blueprint for linking your application to Cloudflare's infrastructure.

A key concept in the Cloudflare Workers ecosystem is Bindings. Bindings securely connect your Worker to services like databases and KV stores without hardcoding credentials. Unlike traditional environment variables, which merely store string values, bindings go further by establishing authenticated connections to Cloudflare services. Think of Bindings as pre-authenticated clients available in your Worker code, not just static configuration values.

Bindings are defined in your Wrangler configuration file and are available in your Worker code inside the env object:

wrangler.json
{
  "kv_namespaces": [
    {
      "binding": "MY_KV",
      "id": "<MY_KV_ID>"
    }
  ]
}
server/api/save-to-kv.ts
export default defineEventHandler(async (event) => {
  const kv = event.context.cloudflare.env.MY_KV;
  await kv.put("my-key", "my-value");
  return "Put successful";
});

Here, MY_KV is a binding that will be injected into your Worker when a request is made, and it will be available in the env object inside your request handler.

Configuration

Wrangler

To configure Wrangler and thus your Worker for Nuxt, you need to create a wrangler.json file in the root of your project. This is my starter template for a Nuxt 3 application:

wrangler.json
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-project",
  "main": "./.output/server/index.mjs",

  "compatibility_date": "2025-02-16", // or later
  "compatibility_flags": ["nodejs_compat"],

  "assets": {
    "directory": "./.output/public/",
    "binding": "ASSETS"
  },

  "observability": {
    "enabled": true,
    "head_sampling_rate": 1
  },
  "upload_source_maps": true,

  "placement": { "mode": "smart" }
}

Here's a breakdown of key Wrangler configuration options:

  • The nodejs_compat compatibility flag enables support for many Node.js built-in modules and APIs within Cloudflare Workers. This is particularly useful when migrating existing Node.js applications or using npm packages that weren't originally designed for Cloudflare's edge runtime. Keep in mind, though, that not all Node.js APIs are fully supported. Always check the compatibility matrix to verify specific APIs.
  • The assets configuration allows your Nuxt app to serve static files (such as images, CSS, JavaScript bundles, and other assets generated during the Nuxt build process) directly from Cloudflare's global edge network.
  • The observability configuration enables logging and monitoring for your Nuxt server running on Cloudflare Workers.
  • The upload_source_maps configuration allows you to upload source maps directly to Cloudflare. Source maps translate minified or transpiled code back into its original, readable form, making your logs and errors more readable.
  • The placement configuration controls where your app runs within Cloudflare's global network. With Smart Placement enabled, Cloudflare intelligently positions your Nuxt Worker closer to your backend infrastructure or databases, optimizing latency and performance.
I'll be using the wrangler.json format in this article as JSON feels more intuitive for most people. However, I am still using wrangler.toml on my projects because some Wrangler commands look for the wrangler.toml file.

TypeScript

To get proper type support for Cloudflare Workers (especially for your bindings), you need to install the @cloudflare/workers-types package and configure your Typescript compiler to use it.

npm install -D @cloudflare/workers-types
tsconfig.json
export default defineNuxtConfig({
  typescript: {
    tsConfig: {
      compilerOptions: {
        types: ['@cloudflare/workers-types/2023-07-01'],
      },
    },
  },
})

The appended date (2023-07-01) corresponds to the specific compatibility date for the Workers runtime. You can also use @cloudflare/workers-types/experimental to get the types for the latest compatibility date. This configuration also allows you to use the wrangler types command to generate types for your Cloudflare Worker bindings, as you will see in the next section.

Finally, you should augment your H3EventContext with Cloudflare-specific properties cf and cloudflare, which will be available in server routes under event.context:

server/types/env.d.ts
import type { CfProperties, ExecutionContext, Request } from "@cloudflare/workers-types";

declare module "h3" {
  interface H3EventContext {
    cf: CfProperties;
    cloudflare: {
      request: Request;
      env: Env;
      context: ExecutionContext;
    };
  }
}
Nitro Cloudflare Dev module will normally automatically augment the H3EventContext with these Cloudflare-specific properties. However, I've found that it sometimes doesn't do it, so I recommend adding this manually.

Scripts

To streamline your development and deployment workflow on Cloudflare Workers, you'll also want to set up some handy scripts in your package.json. These scripts simplify common tasks like deploying your application, generating types, and previewing your Worker locally.

Here's how my own Cloudflare-specific scripts look:

package.json
{
  "scripts": {
    "cf:deploy-prod": "pnpm run build && wrangler deploy",
    "cf:deploy-preview": "pnpm run build && wrangler versions upload",
    "cf:gentypes": "wrangler types ./server/types/worker.d.ts",
    "cf:preview": "pnpm run build && wrangler dev --test-scheduled --port 3000"
  }
}

Let's quickly break down what each script does:

  • cf:deploy-prod: Builds your Nuxt application and deploys it directly to Cloudflare Workers using Wrangler CLI. This is your go-to command for production deployments, unless you've connected a Git repository to your worker on the Cloudflare dashboard.
  • cf:deploy-preview: Creates a preview deployment of your Worker. Ideal for testing changes before pushing them live.
  • cf:gentypes: Generates TypeScript definitions for your Cloudflare Worker bindings based on the wrangler.json file.
  • cf:preview: Builds your application and runs it locally with Wrangler's dev server, simulating scheduled cron events and allowing you to test your Worker locally with edge runtime constraints before deployment. I prefer the 3000 port but you can change it to whatever you want or remove it to use the default 8787 port.

Variables & Secrets

In a typical Nuxt project, you define environment variables in a .env file at the root of your project. Then you load them into your Nuxt runtime configuration via nuxt.config.ts. This configuration system integrates well with Cloudflare's environment variables and secrets, allowing you to maintain a familiar workflow.

.env
MY_ENV_VAR = "my-value";
nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    myEnvVar: process.env.MY_ENV_VAR,
  },
});
You can also define your environment variables with the NUXT_ prefix in your .env file, which will be available both in build and runtime. This is normally the recommended way to define environment variables for Nuxt applications. But I like being a bit more explicit and defining them in the runtimeConfig section.

You can then access these environment variables in your Nuxt application via useRuntimeConfig():

server/api/hello.ts
export default defineEventHandler((event) => {
  return `Hello ${useRuntimeConfig().myEnvVar}`;
});

Cloudflare's own documentation on environment variables was a bit confusing for me at first because it talks about a usual Workers project and not a Nuxt project. Unless you're heavily relying on Wrangler CLI to manage your deployments, you don't need to worry about this. Just follow the Nuxt way of doing things and you'll be fine.

If you're deploying via the Cloudflare dashboard with a Git repository, you need to set up Build variables in the Cloudflare dashboard. The UI is confusing here because it shows a Variables and Secrets section twice: one for Runtime variables at the top and one for Build variables at the bottom. Be sure to scroll down to the Build section to set them up there.

Thanks to Nuxt's runtime configuration, these build variables will also be available at runtime via useRuntimeConfig(). So you don't need to add them to the runtime variables section.

You also have the option to use the Cloudflare way and keep your environment variables in your wrangler.json file and secrets in .dev.vars file for local development. Nitro Cloudflare Dev module will automatically pick them up.

Local Development

Developing locally with Cloudflare Workers introduces a unique challenge: how will you access your bindings and environment variables defined in your Wrangler configuration inside your Nuxt application? Cloudflare injects bindings into your Worker at runtime, so you normally don't have them available locally.

The answer is simple: use the nitro-cloudflare-dev module:

npm i -D nitro-cloudflare-dev
nuxt.config.ts
export default defineNuxtConfig({
  modules: ["nitro-cloudflare-dev"],
});

Nitro Cloudflare Dev module will automatically pick up your bindings from your wrangler.toml file and environment variables from your .dev.vars file for local development.

If you're using wrangler.json instead of wrangler.toml, you need to configure it in the module's options:
nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: "cloudflare-module",
    cloudflareDev: {
      configPath: "wrangler.json",
    },
  },
});

Nitro Preset

While Cloudflare Pages offers a zero-configuration experience, we want to deploy our app as a Worker so we'll need to set the correct Nitro preset:

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: "cloudflare-module",
  },
});

We also need to set the correct compatibility date in Nuxt configuration. This ensures our application aligns with Cloudflare's latest runtime features, including beta support for static assets.

nuxt.config.ts
export default defineNuxtConfig({
  compatibilityDate: "2024-09-19", // or later
});

Deployment

Initially, deploying a Worker directly from GitHub wasn't straightforward. I found myself repeatedly navigating to Cloudflare's "Pages" section, mistakenly assuming I was deploying a Worker.

Thankfully, Cloudflare recently introduced direct Git integration for Workers. You can now connect your GitHub or GitLab repository directly from the Cloudflare dashboard. Once connected, every push to your selected branch automatically triggers a build and deployment.

However, there's still one notable gap: automatic preview deployments for pull requests aren't yet available. While Cloudflare plans to add this feature soon, for now, you'll need to manually create preview URLs if you want to test PR changes before merging (remember the cf:deploy-preview script).

Database and ORM

I have always been a PostgreSQL and Prisma fan. They're familiar, intuitive, and have served me very well. Naturally, I hoped to carry this comfort over into the Cloudflare ecosystem. But things didn't go as planned.

I went down a rabbit hole of trying to find the "perfect" fit for my use case. I tried many combinations of D1, PostgreSQL, Prisma, Drizzle, and Hyperdrive. What began as a simple database decision evolved into a weeks-long exploration of trade-offs between familiarity, performance, and edge compatibility. Each combination offered unique advantages while introducing its own set of challenges:

PrismaDrizzle
D1 (SQLite)• Limited support
• Cumbersome migrations
• No Prisma Studio support
• First-class support
• Streamlined migrations
• Drizzle Studio works with D1
PostgreSQL• Requires Hyperdrive for edge performance
• WASM compatibility issues with Workers
• Needs extra configuration in Nuxt/Nitro
• Requires Hyperdrive for edge performance
• Better Cloudflare edge compatibility
• Simpler configuration
• SQL-like syntax (pro or con depending on preference)

Here's how I worked with the complexities and arrived at a solution that worked for me.

Cloudflare D1

Cloudflare offers its own database solution called D1, an SQLite-compatible database designed specifically for edge computing. D1 is fast, affordable, scalable, and integrates perfectly with Cloudflare Workers. But there were some issues for me:

  • SQLite vs. PostgreSQL: I'm very used to PostgreSQL, and migrating to D1 would mean adopting SQLite.
  • Prisma Migrations: Prisma migrations with D1 are just cumbersome. You can't directly apply migrations using Prisma CLI. Instead, you need to generate SQL migration files with Prisma CLI and then manually apply them using Wrangler CLI.
  • Prisma Studio: Prisma Studio doesn't support D1 databases, neither locally nor remotely. There are community-driven projects like D1 Manager that provide a somewhat viable alternative, but it's not the same like Prisma Studio.

PostgreSQL & Hyperdrive

You can see why sticking with my tried-and-true PostgreSQL + Prisma stack was the clear choice at first. Adding Cloudflare's Hyperdrive offers global query acceleration, everything seemed perfect. But, of course, there were some new issues now:

Enter Drizzle

Given these persistent issues, I explored Drizzle ORM as an alternative. Drizzle seemed to offer several advantages over Prisma in the Cloudflare Workers ecosystem:

  • Better D1 Support: Drizzle provides robust, out-of-the-box support for Cloudflare D1, including Drizzle Studio that works locally and remotely via the D1 HTTP API.
  • Simplified Migrations: Drizzle's migration workflow is straightforward as it can use the D1 HTTP API.
  • Edge Compatibility: Drizzle is designed with edge runtimes in mind, ensuring smooth operation within Cloudflare Workers and Nuxt without additional configuration or experimental flags.

At this point, I was very happy to have found the solution to all my database problems. However, after a few hours of using it, I realized that its SQL-first API felt less intuitive compared to Prisma's expressive query builder. I truly missed Prisma's query syntax, which brought me back to the drawing board.

My Solution

After days of overthinking, I settled on an unconventional yet practical solution:

  • Database: I chose Cloudflare D1. Its tight integration, simplicity, and edge-native performance outweighed PostgreSQL's additional complexity.
  • ORM: I decided to use both Prisma and Drizzle, but for different purposes. Prisma's intuitive query syntax and developer experience remain unmatched for me. Meanwhile, Drizzle's studio provide the missing piece for data inspection and management, both locally and remotely.
Prisma and Drizzle
Prisma ❤️ Drizzle

Adopting two ORMs is a hack, I know. But it provides exactly what I needed: A simple and intuitive query builder with a beautiful studio for data management.

Authentication

Auth Landscape

The Nuxt ecosystem offers several authentication solutions:

  1. Sidebase Auth: An Auth.js/NextAuth.js wrapper I'm familiar with from past projects. Unfortunately, it isn't fully edge-compatible yet, though this may change as they complete their migration from NextAuth to Auth.js.
  2. AuthJS Nuxt: Another Auth.js wrapper that emerged around the same time as Nuxt Auth Utils (I think). While it offers solid functionality, I didn't see any compelling reasons to choose it over the other options.
  3. Nuxt Auth Utils: A simple, lightweight, edge-compatible authentication solution from Atinux himself. For most applications, Nuxt Auth Utils provides everything you need without unnecessary complexity.
  4. Better Auth: An all-in-one authentication solution with a comprehensive feature set including multi-factor authentication, fine-grained permissions, and an intuitive API. Its plugin ecosystem makes it highly extensible while maintaining developer experience.

Attempting Better Auth

Initially, I tried using Better Auth because of the shiny object syndrome its advanced features and comprehensive capabilities, thinking they might come in handy later, if not now. Of course, I quickly ran into several issues, though it was possible to work around them.

  • PostgreSQL/Prisma Compatibility: Better Auth works great with both PostgreSQL and Prisma. But as soon as you deploy it to Cloudflare Workers (or just run wrangler dev), you get a cryptic The script will never generate a response error. After digging through GitHub issues, I found out that this was due to Cloudflare's CPU time constraints as explained by Better Auth's creator Bereket Engida. Surprisingly, even custom hashing functions and alternative login methods failed to resolve this for me.
  • Globally Scoped Client Instances: Better Auth expects globally scoped clients on both client and server sides. This proved particularly challenging on the server side, where you don't have access to the Cloudflare context (hence D1 or Hyperdrive) in global scope. Inspired by Atinux's NuxtHub implementation, I created a client-side composable that uses a globally scoped client instance and implemented a singleton pattern on the server side to maintain a consistent client across all requests.
    app/composables/auth.ts
    import { createAuthClient } from "better-auth/vue";
    
    const client = createAuthClient();
    
    export function useAuth() {
      async function getSession() {
        const { data, error } = await client.useSession(useFetch);
    
        if (error.value) {
          throw createError({
            statusCode: error.value.status,
            statusMessage: error.value.statusText,
            message: error.value.message,
            fatal: true,
          });
        }
    
        return data;
      }
    
      async function signUp() { }
    
      async function signIn() { }
    
      async function signOut() { }
    
      return { getSession, signUp, signIn, signOut };
    }
    
    server/utils/auth.ts
    import type { H3Event } from "h3";
    import { betterAuth } from "better-auth";
    import { prismaAdapter } from "better-auth/adapters/prisma";
    
    let _auth: ReturnType<typeof betterAuth>;
    
    export function useServerAuth(event: H3Event) {
      if (!_auth) {
        const { authSecret, public: { app: { url } } } = useRuntimeConfig(event);
        const prisma = useDatabase(event);
    
        const auth = betterAuth({
          secret: authSecret,
          baseURL: url,
          database: prismaAdapter(prisma, {
            provider: "postgresql", // or "sqlite" for D1
          }),
          emailAndPassword: {
            enabled: true,
          },
        });
    
        _auth = auth;
      }
    
      return _auth;
    }
    
  • WebAssembly Dependencies: Adding Better Auth to my project unexpectedly required enabling the experimental WebAssembly flag in my Nitro configuration. Interestingly, this wasn't necessary when using the lighter-weight Nuxt Auth Utils.

Embracing Nuxt Auth Utils

After some point, I finally realized that I don't need all the features of Better Auth. I just needed a simple, lightweight, and edge-compatible authentication solution. And that's exactly what Nuxt Auth Utils provides.

It is called "utils" for a reason. It is not a full-fledged authentication solution like Better Auth. Instead, it is a set of utilities to help you implement authentication in your Nuxt application. This reduces externalities and makes it easier to reason about the codebase.

For anyone in a similar situation, I recommend Nuxt Auth Utils as the default choice for most projects and only reach for Better Auth if you really need its advanced features.

Pitfalls & Learnings

Nuxt Image

I could not get Nuxt Image to work with Cloudflare Workers at first. After hours of troubleshooting, I finally discovered the missing piece of the puzzle.

For Cloudflare to transform images, you must enable Image Transformations for your domain in the Cloudflare dashboard under Images > Transformations. If you plan to use images from external domains (like your CDN or third-party services), you also need to enable Resize Image from Any Origin.

Transformations are available on Cloudflare's free plan, but it's not enabled by default. Once activated, configuring Nuxt Image becomes straightforward:

nuxt.config.ts
export default defineNuxtConfig({
  image: {
    format: ["webp"],
    provider: "cloudflare",
    cloudflare: {
      baseURL: "https://your-domain.com", // Your deployment's domain
    },
  },
});
Don't worry about what Images documentation says about base url (zone). It is simply the domain (base url) of your deployment.

Sentry

Sentry has always been my go-to error tracking solution. And naturally, I wanted to use it with Cloudflare Workers. Client-side error tracking with Sentry works seamlessly (either via @sentry/nuxt module or manual integration via @sentry/vue). However, the server-side is a different story.

The standard @sentry/node doesn't work with Cloudflare Workers due to edge runtime constraints. But after some investigation, I found two solutions:

  1. Toucan.js: A lightweight Sentry client designed specifically for edge environments. This became my preferred option.
  2. Cloudflare Tail Workers: Cloudflare offers automatic error forwarding to Sentry through their Tail Workers integration. While promising in theory, it was impossible for me to set up due to consistent UI bugs in the process.

In the end, the combination of the Sentry Nuxt module for client-side and Toucan.js for server-side provided reliable error tracking across my entire application.

Prisma Client

Prisma on the edge was a pain. The issue was not that it was incompatible with the edge runtime, but that there were too many elements in the stack I had to configure to get it to work. Here are my learnings:

  • You might think that you need to use the @prisma/client/edge package. But you don't because it apparently is reserved only if you're using Prisma Accelerate (more on that below). You need to use the @prisma/client package but with adapters as defined in the Prisma docs.
  • If you want a globally distributed database, you either use Cloudflare Hyperdrive or Prisma Accelerate. Using Accelerate is more straightforward but if you want the full Cloudflare experience, you need to use Hyperdrive with the relevant Prisma adapter.
  • For PostgreSQL adapters, take a look at the more specific adapters before trying out the PostgreSQL (traditional) adapter. For instance, if you're migrating from Vercel, you'll need the Neon adapter.
  • In most cases, you will need to enable the experimental WebAssembly flag in your Nitro configuration and might need to mark some modules as external in the Rollup configuration (but again, this might have been resolved in the latest Nitro release).

Cron Jobs

Cloudflare's cron triggers are great for running scheduled tasks. And you can use them in a Nuxt project by using Nitro's experimental tasks feature. Here are the steps to set it up:

  1. Define a task in the server/tasks directory:
    server/tasks/myTask.ts
    export default defineTask({
      meta: {
        name: "my-task",
        description: "My task description",
      },
      run({ payload, context }) {
        // do something
        return { result: "Success" };
      },
    });
    
  2. Enable tasks in your Nitro configuration and specify scheduled tasks:
    nuxt.config.ts
    export default defineNuxtConfig({
      nitro: {
        experimental: { tasks: true },
        scheduledTasks: {
          "* * * * *": ["my-task"], // Make sure to specify the task name as defined in the task file
        },
      },
    });
    
  3. Define a cron trigger in your Wrangler configuration using exactly same pattern you defined in scheduledTasks:
    wrangler.toml
    [triggers]
    crons = [ "* * * * *" ]
    
See Atinux's example here for a more complete example.

Was It Worth It?

Absolutely—just maybe not for the reasons you'd expect.

More than performance or cost, what truly attracted me to Cloudflare was the unified experience its platform provides. It offers everything I need, all seamlessly integrated within a single, cohesive ecosystem. This cohesion means fewer moving parts, less time spent hopping between disparate platforms, and more energy spent on building and shipping.

Sure, navigating the Cloudflare ecosystem initially felt like too much to handle, but each bump clarified my understanding. Rather than fighting the complexity of scattered external services, I had a single, integrated ecosystem—one cohesive mental model to master. Once you get the Cloudflare way of deploying apps to the edge, things will fall into place.

For those considering a similar migration, my advice is simple: start small. Deploy a lightweight API endpoint or static site first. Experiment and build familiarity step-by-step. Diving headfirst into a full migration risks drowning in configuration complexity, making issues difficult to pinpoint.

Special thanks to Daniel Roe for providing feedback on the Nuxt configuration section. Any (remaining) inaccuracies are my own.

© 2025 Yusuf Mansur Özer