Nuxt 4 + Tailwind + Cloudflare. Sounds like the dream stack, right?
When I decided to rebuild BulkPicTools, that’s exactly what I thought. "Nuxt 4 is built for speed," I told myself. "This is going to be an easy win."
Well, reality hit me pretty hard.
Honestly, this is the "Bleeding Edge Tax." Everything runs perfectly on localhost, but the moment you try to deploy, things start breaking. Since I’ve already spent the sleepless nights fixing this stuff, I’m writing it down so you (hopefully) don’t have to.
1. That Lighthouse Score That Hurt My Feelings
When I deployed the first version, I was feeling pretty good. The UI was snappy, Tailwind was doing its job. I ran a Lighthouse test just to see that sweet 100/100.
Score: < 60. 🔴

(Caption: Ouch. The initial mobile performance score was a disaster.)
I was shocked. Nuxt 4 is supposed to be a performance beast!
I stared at the screen for a solid minute. Isn't Nuxt 4 supposed to be a performance beast?
Turns out, it was the CSS. By default, the build was generating a bunch of separate CSS files for Tailwind. The browser had to wait for all those network requests before it could paint anything. My First Contentful Paint (FCP) was trash.
The fix was a bit aggressive: I forced the styles directly into the HTML.
I stopped using <style> blocks in my Vue components entirely (strict discipline required) and forced Nuxt to inline the Tailwind config.
Here is the "magic switch" I added to nuxt.config.ts:
// nuxt.config.ts
export default defineNuxtConfig({
// Centralize all Tailwind styles here
css: ['~/assets/css/tailwind.css'],
features: {
// This is the lifesaver: inline the styles to kill network requests
inlineStyles: true
},
})
After this? The score shot back up to 90+. It feels a bit weird to inline everything in 2025, but hey, if it makes the site fast, I’ll take it.

Finally seeing green. The "Inline Styles" config brought the mobile score from <60 back to 99.
2. The "It Works on My Machine" Nightmare (Cloudflare Edition)
This is the sentence every developer dreads.
I chose Cloudflare Pages because it’s free and fast. Locally, npm run generate worked flawlessly. I pushed to GitHub, poured a coffee, and waited for the green checkmark.
Build Failed. 💥
The error was complaining about a missing queryCollection. This is a core function I use to fetch blog content. It turns out Cloudflare’s build environment handles module resolution differently than my local Mac.
Did I have time to debug Cloudflare's internal Node environment? No.
So, I decided to "lie" to the compiler.
I wrote a Mock Adapter to trick the build process into thinking those modules existed.
First, I aliased the missing modules in the config:
// nuxt.config.ts
import { fileURLToPath } from 'url'
export default defineNuxtConfig({
alias: {
// Can't find it? Redirect to my fake file.
'@nuxt/content/server': fileURLToPath(new URL('./adapter-content.ts', import.meta.url)),
'@nuxt/content/dist/module.mjs': fileURLToPath(new URL('./adapter-content.ts', import.meta.url))
},
})
Then, I created the "liar" file, adapter-content.ts:
// adapter-content.ts
// 1. Mock queryCollection for the Content v3 API
export const queryCollection = () => {
return {
all: () => Promise.resolve([]),
where: () => ({
all: () => Promise.resolve([])
})
}
}
// 2. Mock the legacy API too, just in case the Sitemap module asks for it
export const serverQueryContent = () => {
return {
find: () => Promise.resolve([]),
where: () => ({
find: () => Promise.resolve([])
})
}
}
export default {
queryCollection,
serverQueryContent
}
Is it a dirty hack? Yes. Did it work? Absolutely. The build passed, and the site went live. Good enough for me.
3. Launching a Landing Page in 30 Minutes? You Can't Code That by Hand
My plan is to launch dozens of specific tools, like "WebP to JPG" or "HEIC to PNG."
If I had to manually code the Title, Meta Description, JSON-LD Schema, and i18n support for every single one... I’d be stuck writing boilerplate code for the rest of my life. Plus, trends move fast. If I see a keyword opportunity, I need that page live in under 30 minutes.
So I stopped writing pages and started writing config.
I built a Configuration-Driven Engine:
- The Config: Everything is a JSON object. Input format, output format, FAQs, titles.
-
The Template: One generic
.vuefile that reads the config. - The SEO Layer: This is the secret sauce. The template automatically generates complex JSON-LD based on the config.
Breadcrumbs? Generated from the route.
Tool Schema? Filled in automatically.
Translations? It looks up the keys based on the ID.
Now, if I want to launch a new tool, I just commit a few lines of JSON. That’s how it should be.
Building with bleeding-edge tech like Nuxt 4 is a bit of a rollercoaster. You get the incredible performance, but you also have to be ready to patch the holes in the ecosystem yourself.
Is the code perfect? Definitely not. But BulkPicTools is live, it loads instantly, and I didn't lose all my hair building it.
Want to see if my hacks held up? Go check it out:
👉 BulkPicTools.com


Top comments (0)