When working with modern web frameworks, module conflicts can be frustrating roadblocks that consume hours of development time. Recently, I encountered a particularly tricky issue while setting up a Nuxt 4 project with both the nuxt-aos
module and @nuxtjs/tailwindcss
module. What seemed like a straightforward setup quickly turned into a debugging nightmare when Tailwind CSS stopped working properly, color mode functionality broke, and AOS animations refused to animate.
If you've stumbled upon this post, chances are you're facing similar issues. Let me walk you through the problem and the solution that finally got everything working seamlessly.
The Problem: When Modules Collide
I was building a Nuxt 4 application that required:
- Tailwind CSS for styling and responsive design
- Color mode functionality for dark/light theme switching
- AOS (Animate On Scroll) for smooth scroll animations
Initially, I installed both the nuxt-aos
module and @nuxtjs/tailwindcss
module, expecting them to work together harmoniously. Instead, I encountered:
- Tailwind CSS classes not applying - Styles weren't loading correctly
- Color mode switching broken - Dark/light theme toggle stopped functioning
- AOS animations not working - Scroll animations weren't triggering
The root cause appeared to be module initialization conflicts and CSS loading order issues between the automated module configurations.
The Solution: Manual Plugin and CSS Configuration
After extensive debugging, I discovered that the issue stemmed from module conflicts and initialization timing. The solution involved:
-
Removing the
nuxt-aos
module from the modules array - Creating a custom AOS client plugin for proper initialization
- Manually configuring Tailwind CSS with explicit CSS file paths
Here's the complete working configuration:
1. Updated Nuxt Configuration
// nuxt.config.ts
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
modules: [
'@nuxt/icon',
'@nuxt/image',
'@nuxtjs/tailwindcss',
'@nuxtjs/color-mode',
'@nuxtjs/supabase',
'nuxt-swiper',
'@vueuse/nuxt',
['@nuxtjs/google-fonts',{
families: {
Inter: '100..900'
}
}],
// Note: nuxt-aos module removed from here
],
supabase: {
redirect: false,
redirectOptions: {
login: '/auth/login',
callback: '/auth/confirm',
exclude: ['/', '/auth/login', '/auth/confirm', '/auth/register', '/auth/forgot-password', '/auth/reset-password'],
saveRedirectToCookie: true
}
},
css: [
'aos/dist/aos.css',
'~/assets/css/tailwind.css',
],
tailwindcss: {
cssPath: [
'~/assets/css/tailwind.css',
{
injectPosition: 'last'
},
],
},
app: {
pageTransition: {
name: 'page',
mode: 'out-in'
}
}
})
2. Custom AOS Client Plugin
Instead of relying on the nuxt-aos
module, I created a custom client-side plugin:
// plugins/aos.client.ts
import AOS from 'aos'
import 'aos/dist/aos.css'
export default defineNuxtPlugin(() => {
AOS.init({
// Global settings:
disable: false,
startEvent: 'DOMContentLoaded',
initClassName: 'aos-init',
animatedClassName: 'aos-animate',
useClassNames: false,
disableMutationObserver: false,
debounceDelay: 50,
throttleDelay: 99,
// Per-element settings:
offset: 120,
delay: 0,
duration: 400,
easing: 'ease',
once: false,
mirror: false,
anchorPlacement: 'top-bottom',
})
const refreshAos = () => AOS.refresh()
const refreshHardAos = () => AOS.refreshHard()
return {
provide: {
refreshAos,
refreshHardAos,
},
}
})
3. Explicit Tailwind CSS File
I created a dedicated Tailwind CSS file with explicit directive imports:
/* ~/assets/css/tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Key Insights and Why This Works
1. Module Initialization Conflicts
The nuxt-aos
module was interfering with Tailwind CSS initialization. By removing it and creating a custom plugin, I gained full control over when and how AOS initializes.
2. CSS Loading Order
Explicitly defining the CSS path in the Tailwind configuration with injectPosition: 'last'
ensures that Tailwind styles load after other CSS, preventing style conflicts.
3. Client-Side Plugin Benefits
The .client.ts
suffix ensures AOS only initializes on the client side, preventing SSR hydration mismatches that could break animations.
4. Provide/Inject Pattern
The plugin exposes refreshAos()
and refreshHardAos()
methods globally, allowing you to manually refresh AOS when needed (useful for dynamic content).
Usage in Components
With this setup, you can now use AOS animations in your components normally:
<template>
<div>
<div data-aos="fade-up" data-aos-duration="1000">
This will fade up on scroll
</div>
<div data-aos="slide-left" data-aos-delay="200">
This will slide from left with delay
</div>
</div>
</template>
You can also programmatically refresh AOS when needed:
<script setup>
// Access the provided methods
const { $refreshAos, $refreshHardAos } = useNuxtApp()
// Refresh AOS after dynamic content changes
const loadMoreContent = async () => {
await fetchMoreData()
$refreshAos()
}
</script>
Additional Dependencies
Don't forget to install the AOS package directly:
npm install aos
# or
yarn add aos
# or
pnpm add aos
Conclusion
Module conflicts in Nuxt can be tricky to debug, but understanding the underlying initialization and CSS loading processes helps identify solutions. By taking manual control over AOS initialization and explicitly configuring Tailwind CSS paths, we achieved a stable setup where:
- ✅ Tailwind CSS classes work perfectly
- ✅ Color mode switching functions correctly
- ✅ AOS animations trigger smoothly
- ✅ All modules coexist without conflicts
This approach gives you more control over your dependencies while maintaining the benefits of Nuxt's module ecosystem. Sometimes the best solution is to step back from automated configurations and implement things manually with a deeper understanding of what's happening under the hood.
Have you encountered similar module conflicts in your Nuxt projects? Share your experiences and solutions in the comments below!
Happy coding! 🚀
Top comments (0)