I barely have time to work on my personal portfolio—let alone debug production issues. This post is a quick look at a frustrating but valuable learning experience I had while trying to get my Nuxt-powered site to behave properly in production.
Building my personal portfolio with Nuxt seemed like a great choice—modern, clean, and perfect for SEO with its slick SSR. I used Nuxt Content for managing dynamic content and deployed via Nuxt Hub on Cloudflare, relying on their promise of smooth integration.
I noticed that the landing page of my portfolio—which includes sections that load dynamic data from Nuxt Content —was failing in production. These sections work flawlessly in development but fail to load on production, leading to hydration mismatch errors and even my site 404ing on every fresh route hit except for the index page. To make matters worse, these sections seemed to load just fine sometimes, and sometimes they just refused to load silently without any errors logged.
I couldn't narrow down the hydration mismatch error to a single root culprit and dived in a cycle of fixing random errors related to how I structured the html in these sections, but the issues didn't disappear.
After hours of research, going through GitHub issues, and searching in old threads on the Nuxt Discord server. I felt like I was debugging the internals of the promised "plug and play" system. I found hope when I noticed that while running a local simulation of the production environment using npx nuxthub preview
. I noticed the wrangler's DevTool shows the following error message whenever I refresh the page - cold starting and kicking the SSR mechanism of Nuxt (more on this later).
H3Error: [db0] [d1] binding `DB` not found
For Context:
npx nuxthub preview
is a utility to quickly scaffold a local simulation of the production environment using Cloudflare's warngler.
Nuxt Content uses a SQL-based approach to store and serve content, choosing different adapters based on the environment. In development, it defaults to SQLite, which works seamlessly without any setup. In production, however, it expects a different adapter like Cloudflare D1 or Postgres.
After many failed attempts and hours of CPU time wasted on building and debugging production, I had to manually enable the database in the Nuxt Hub config and also set the database binding myself. The fix to my issues was to simply add these lines to the nuxt.config.ts
// ....
content: {
database: {
type: 'd1',
bindingName: 'DB',
},
},
hub: {
// ...
database: true,
},
Of course, I also had to set up the binding DB
on the Cloudflare dashboard for my Worker manually.
But according to the docs, Nuxt Content "automatically enables the NuxtHub database" and configures Cloudflare D1 for you. Here is an excerpt from the docs page on how to deploy a Nuxt Content app via NuxtHub;
Nuxt Content module automatically enables NuxtHub database. And update database configuration to use Cloudflare D1 with
DB
binding name. (This is default configuration for NuxtHub database.)You can override the database configuration by providing your own database configuration in
nuxt.config.ts
.
To make things more frustrating, The local preview tool (npx nuxthub preview
) also failed to mirror production accurately. At the time of writing, it still fails to load the dynamic section whilst the actual production does, though it was the sole helper when everything failed me including modern day GPTs 😏.
And I also figured out that these sections could load just fine sometimes because Nuxt Content does seem to have a fallback—if the data is fetched on the client-side e.g., in the context of a <ClientOnly>
or a component/page with .client.vue
suffix. This is achieved by Nuxt Content serving a binary dump of the SQLite file. Some GitHub issues suggested turning the whole site into a client-rendered app to avoid the problem. But I didn’t want to give up on SSR and SEO just for convenience.
Throughout all this I learned that “it works on my machine” is a big deal when cloud bindings and serverless functions are involved. But, using docker for a portfolio is an overkill so I take it as is.