Client-only Nuxt 3 Vue plugin - javascript

I am new to Nuxt and Vue, so go easy on me. I am trying to create a video player component in my Nuxt 3 app using vue3-video-player, which doesn't seem to support SSR based on the following error I get when I import it in my video component:
ReferenceError: navigator is not defined
This error persists even if the component is wrapped with <ClientOnly>. So, based on what I saw in the Nuxt 3 Documentation I thought I would create a client-only plugin located at plugins/vue3-video-player.client.js with the following contents:
import Vue3VideoPlayer from '#cloudgeek/vue3-video-player'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Vue3VideoPlayer)
})
But when I try to use it in my component as <vue3-video-player>, I get the following error:
[Vue warn]: Failed to resolve component: vue3-video-player
So I guess my question is how do I create a client-only Vue component using Nuxt 3 plugins? Or is there an entirely different approach that would work better?

Solution for nuxt 3:
Nuxt will automatically read the files in your plugins directory and load them. You can use .server or .client suffix in the file name to load a plugin only on the server or client side.
For example:
plugins/apexcharts.client.ts
Everything is so simple! And that is why we love nuxt ❤️
Solution for nuxt 2 (to show the difference):
nuxt.config.ts
plugins: [
{src: '~/plugins/apexcharts', mode: 'client'}
],
This is only for nuxt 2 because All plugins in your nuxt 3 plugins/ directory are auto-registered, so you should not add them to your nuxt.config separately.

you could try to provide a helper function. As mentioned in the docs.
import Vue3VideoPlayer from '#cloudgeek/vue3-video-player'
export default defineNuxtPlugin((nuxtApp) => {
return {
provide: {
Vue3VideoPlayer
}
}
})

To tag along with the given correct answer here,
If you're trying to install and use a third party NPM package, and running into "window is not defined" type errors, you can load the package as a plugin as follows (eg WAD)
npm install web-audio-daw
// plugins/wad.client.ts
import Wad from "web-audio-daw"
export default defineNuxtPlugin(nuxtApp => {
return {
provide: {
Wad,
}
}
})
// pages/whatever.vue
<script lang="ts" setup>
const { $Wad } = useNuxtApp();
// Can use $Wad normally from here on out
</script>

Related

Can't import Vonage Client SDK into my preact (vite) project

I have a Preact project using Vite. I want to use the nexmo-client SDK from vonage but when I import using the ES method it breaks my project.
// app.tsx
import NexmoClient from 'nexmo-client';
I get the following error in the console.
index.js:19 Uncaught ReferenceError: global is not defined
at index.js:19:19
at index.js:12:22
at node_modules/nexmo-client/dist/index.js (index.js:16:1)
at __require (chunk-J43GMYXM.js?v=f3505250:11:50)
at dep:nexmo-client:1:16
However if I import it using via a script tag it works just fine.
// index.html
<script src="node_modules/nexmo-client/dist/nexmoClient.js"></script>
// app.tsx
const NexmoClient = window.NexmoClient;
OK, there are two problems here.
Firstly, NexmoClient tries to use global which is not available on the browser.
Secondly, NexmoClient has a dependency on socket.io-client, for some reason Vite imports a Node version of the socket.io-client that again tries to use modules that are not available on the browser, namely 'child_process'.
To fix both issues you can provide the following config to Vite, this should make sure the resulting build is compatible with the brother.
// vite.config.js or vite.config.ts
import { defineConfig } from 'vite'
import preact from '#preact/preset-vite'
export default defineConfig({
plugins: [preact()],
define: {
global: {},
},
resolve: {
alias: {
"xmlhttprequest-ssl": "./node_modules/engine.io-client/lib/xmlhttprequest.js"
}
}
})

Can't generate Nuxt website with #googlemaps/js-api-loader

I am using #googlemaps/js-api-loader in my Nuxt 3 website. Everything works fine in local development, but when I try to build the project with nuxt generate (no matter if locally or on Vercel) I'm getting following error:
[nuxt] [request error] Named export 'Loader' not found. The requested module 'file:///path/to/website/node_modules/#googlemaps/js-api-loader/dist/index.umd.js' is a CommonJS module, which may not support all module.exports as named exports. CommonJS modules can always be imported via the default export, for example using:
The important part of loading script looks like this:
import { Loader } from '#googlemaps/js-api-loader';
const loader = new Loader({
apiKey: config.googleMapsApiKey,
version: 'weekly',
});
onMounted(async() => {
await loader
.load()
...
so I tried to import this package differently, e.g.:
import * as gmaps from '#googlemaps/js-api-loader';
const { Loader } = gmaps;
and the previous error disappeared, but now I'm getting
[Vue warn]: Unhandled error during execution of setup function
at <DynamicLocations class="contact__map" locations= [
{
id: 1,
...
[nuxt] [request error] gmaps.Loader is not a constructor
at setup (./.nuxt/prerender/chunks/app/server.mjs:5536:20)
at _sfc_main$t.setup (./.nuxt/prerender/chunks/app/server.mjs:5582:25)
at callWithErrorHandling (./.nuxt/prerender/chunks/renderer.mjs:2654:23)
at setupStatefulComponent (./.nuxt/prerender/chunks/renderer.mjs:9548:30)
at setupComponent (./.nuxt/prerender/chunks/renderer.mjs:9503:12)
at renderComponentVNode (./.nuxt/prerender/chunks/renderer.mjs:12068:17)
at Object.ssrRenderComponent (./.nuxt/prerender/chunks/renderer.mjs:12504:12)
at ./.nuxt/prerender/chunks/app/server.mjs:5628:36
at renderComponentSubTree (./.nuxt/prerender/chunks/renderer.mjs:12149:13)
at renderComponentVNode (./.nuxt/prerender/chunks/renderer.mjs:12084:16)
I also can't import package by default export. Do you have any ideas what's going on and how can I fix this?
I found a documentation page related to this problem and there is the following information:
If you encounter these errors, the issue is almost certainly with the upstream library. They need to fix their library to support being imported by Node.
Although they provide a solution to get rid of errors by adding the package to build.transpile:
build: {
transpile: ['#googlemaps/js-api-loader'],
},
It worked for me

Nuxtjs custom module

I'm quite new to Nuxtjs so I made a test project which purpose is merely the (of course) testing of Nuxtjs functionalities.
Currently I'm trying to create a simple custom module: afaik a module is basically a wrapper around a vou/js library/plugin, something like a high-level integration used to expose configurations on how the underlying library/plugin is imported and used in the Nuxt application.
So I'm trying with a simple module that declare some plain js classes that I'll use in my application, e.g. Order and Product, and that's what I came out with:
Directory structure
pages
the-page.vue
modules
classes
index.js
order.js
/modules/classes/index.js
const path = require('path')
export default function (moduleOptions) {
const { nuxt } = this
// add the debug plugin
this.addPlugin({
src: path.resolve(__dirname, 'order.js'),
})
}
/modules/classes/order.js
class Order {
constructor(id) {
this.id = id;
console.log('created order #' + this.id);
}
}
export {Order};
/nuxt.config.js
export default {
// ...
buildModules: [
// ...
'~/modules/classes'
],
// ...
}
/pages/the-page.vue
<script>
export default {
name: 'ThePage',
data () {
return {
}
},
methods: {
createOrder () {
const order = new Order(123)
}
}
}
</script>
The error
My defined class are still not imported in my pages:
/app/pages/the-page.vue
18:13 error 'order' is assigned a value but never used no-unused-vars
18:25 error 'Order' is not defined no-undef
Considerations
Probably I'm missing something about modules usage and/or implementation, but every tutorial I found starts with too complex scenarios, and since I'm at the beginning with Nuxtjs I need something easier to implement.
Ok, I found out that I was mistaken how NuxtJs modules are intended to work and was traying to do somenthing they are not intended for.
Nuxt modules cannot import js classes in every component of the application as I wanted to do, they just "add a property" to the main application instance that is made accessible through this.$<something>, like e.g. you can already do in simple Vue with the Vue Router or the Vuex store plugins that give access to the this.$router and this.$store properties.
NuxtJs modules just wrap simple plugins and expose configuration options to made.

Storybook - Cannot Mock a Nested React-Router-Dom Link without throwing endless NPM errors

I am trying to implement StorybookJS into a SSR React app. Basic components work fine (button, headers etc). But anything that nests using dependencies like react-router-dom breaks.
Example:
We have a custom built <Link /> component that manages external links with a ternary. The external links flip to <a href= while internals use react-router-dom's <Link> imported as <ReactLink />. That code is like this:
// src/client/components/link/Link.js
import { Link as ReactLink } from "react-router-dom";
import { isLinkExternal } from "services/utils";
export const Link = ({ href, children = null, ...props }) => {
return isLinkExternal(href) ? (
<a href={href} {...props}>
{children}
</a>
) : (
<ReactLink to={href} {...props}>
{children}
</ReactLink>
);
};
The StorybookJS file for it looks like this:-
// link.stories.js
import React from "react";
import { Link } from "./Link"; // importing my component
export default {
title: "My Components/Link",
component: Link, // assigning my component
};
export const MyStoryBookLink = () => <Link href="/foo">I am a link</Link>;
Now, when i run Storybook it throws a load of errors, here are the recurring/main ones:-
ERROR in ./node_modules/redis-parser/lib/hiredis.js
Module not found: Error: Can't resolve 'hiredis' in '/Users/me/Documents/my-proj/node_modules/redis-parser/lib'
...
...
# ./.storybook/generated-stories-entry.js
I haven't touched anything redis / hiredis related and there is no such file as generated-stories-entry.js. The app works perfectly in Dev and Production so this is exclusively a Storybook env issue.
Next error down:
ERROR in ./node_modules/cache-manager-ioredis/node_modules/ioredis/lib/connectors/connector.js
Module not found: Error: Can't resolve 'net' in '/Users/me/Documents/myProject/node_modules/cache-manager-ioredis/node_modules/ioredis/lib/connectors'
Again, Though we are using cache-manager-ioredis, no idea why this is suddenly missing a module if it works fine on the app itself and all i'm trying to do is render a .
Next one:
Module not found: Error: Can't resolve 'tls' in cache-manager-ioredis
Same thing again^^
Then i get a load of these:
/Users/me/Documents/myProj/__mocks__/hiredis doesn't exist
.mjs
/Users/me/Documents/myProj/__mocks__/hiredis.mjs doesn't exist
.js
/Users/me/Documents/myProj/__mocks__/hiredis.js doesn't exist
.jsx
/Users/me/Documents/myProj/__mocks__/hiredis.jsx doesn't exist
.ts
/Users/me/Documents/myProj/__mocks__/hiredis.ts doesn't exist
.tsx
/Users/me/Documents/myProj/__mocks__/hiredis.tsx doesn't exist
.json
/Users/me/Documents/myProj/__mocks__/hiredis.json doesn't exist
.cjs
/Users/me/Documents/myProj/__mocks__/hiredis.cjs doesn't exist
Suggests it's looking for mocks to cover these sub sub sub dependencies, wherever they're needed.
I get the same for net and tls.
Finally, I get some:
Field 'browser' doesn't contain a valid alias configuration
I'm thinking somewhere in the depths of using react-router-dom/Link it is trying to find these, and they would only be there if webpack dev server / hot reloading made them accessible, OR if they were transpiled to be accessible from the production bundle.
But how do I mock these out? And is there an easy way to do it rather than manually mocking every sub dependency?
I have tried:
adding __mocks__/react-router-dom.js with an export const Link = ({props}) => <div>{children}</div> but it doesnt seem to kick in.
adding alias logic to .storybook/main.js:
webpackFinal: (config) => {
config.resolve.alias['react-router-dom'] = require.resolve('../__mocks__/react-router-dom.js');
return config;
},
Again, nothing seems to change.
using the storybook-react-router pkg but this seems quite old now, it configs to an old config.js file rather than main.js and uses the older storiesOf syntax. Also couldn't get to do anything.
manually installed tls, hiredis etc as --save-dev dependencies. But this seems hack. Why are these modules missing?
I cannot believe Storybook is this hard to use, more likely I'm overlooking something. I just want to mock something as common and basic as a from RRD.
What am I doing wrong? What am I missing?
I think I found the reason. It is because of node.js packages. To make it work, there are 2 solutions.
avoid importing node.js packages (usually related to SSR) for storybook related code. I use NX to structure my code, so I can easily move those part to its own library and only reference it from the top. (No storybook for the top App either in this solution)
skip those packages in the config.
something like
config.resolve.fallback = { http: false, net: false, tls: false, fs: false, dns: false, path: false };

"document is not defined" in Nuxt.js

I am trying to use Choices.js within a Vue component. The component compiles successfully, but then an error is triggered:
[vue-router] Failed to resolve async component default:
ReferenceError: document is not defined
In the browser I see:
ReferenceError document is not defined
I think this has something to do with the SSR in Nuxt.js? I only need Choices.js to run on the client, because it's a client only aspect I guess.
nuxt.config.js
build: {
vendor: ['choices.js']
}
AppCountrySelect.vue
<script>
import Choices from 'choices.js'
export default {
name: 'CountrySelect',
created () {
console.log(this.$refs, Choices)
const choices = new Choices(this.$refs.select)
console.log(choices)
}
}
</script>
In classic Vue, this would work fine, so I'm very much still getting to grips with how I can get Nuxt.js to work this way.
Any ideas at all where I'm going wrong?
Thanks.
It's a common error when you start a Nuxt project ;-)
The Choices.js lib is available only for client-side! So Nuxt tried to renderer from server-side, but from Node.js window.document doesn't exist, then you have an error.
nb: window.document is only available from the browser renderer.
Since Nuxt 1.0.0 RC7, you can use <no-ssr> element to allow your component only for client-side.
<template>
<div>
<no-ssr placeholder="loading...">
<your-component>
</no-ssr>
</div>
</template>
take a look at the official example here: https://github.com/nuxt/nuxt.js/blob/dev/examples/no-ssr/pages/index.vue
Update:
Since Nuxt >= 2.9.0, you have to use the <client-only> element instead of <no-ssr>:
<template>
<div>
<client-only placeholder="loading...">
<your-component>
</client-only>
</div>
</template>
To know more, see nuxt docs: https://nuxtjs.org/docs/2.x/features/nuxt-components#the-client-only-component
The accepted answer (while correct) was too short for me to understand it and use it correctly, so I wrote a more detailed version. I was looking for a way to use plotly.js + nuxt.js, but it should be the same as the OP's problem of Choice.js + nuxt.js.
MyComponent.vue
<template>
<div>
<client-only>
<my-chart></my-chart>
</client-only>
</div>
</template>
<script>
export default {
components: {
// this different (webpack) import did the trick together with <no-ssr>:
'my-chart': () => import('#/components/MyChart.vue')
}
}
</script>
MyChart.vue
<template>
<div>
</div>
</template>
<script>
import Plotly from 'plotly.js/dist/plotly'
export default {
mounted () {
// exists only on client:
console.log(Plotly)
},
components: {
Plotly
}
}
</script>
Update: There is <client-only> tag instead of <<no-ssr> in Nuxt v>2.9.0, see #Kaz's comment.
You need to add it as a plugin and then disable SSR for it.
As the document and window are not defined on the server-side.
Your nuxt.config.js should look like below
plugins: [
{ src: '~/plugins/choices.js' } // both sides
{ src: '~/plugins/client-only.js', mode: 'client' }, // only on client side
{ src: '~/plugins/server-only.js', mode: 'server' } // only on server side
],
I found that now the no-ssr is replace by , i am using echart and have the same problem but now it´s working!
<client-only>
<chart-component></chart-component>
</client-only>
I had this error with lightgallery.js adding mode: 'client'
seems helped
nuxt.config.js
plugins: [
{ src: '~/plugins/lightgallery.js', mode: 'client' }
],
plugins/lightgallery.js
import Vue from 'vue'
import lightGallery from 'lightgallery.js/dist/js/lightgallery.min.js'
import 'lightgallery.js/dist/css/lightgallery.min.css'
Vue.use(lightGallery)
ImageGallery.vue
<template>
<section class="image-gallery-container">
<div class="image-gallery-row">
<div
ref="lightgallery"
class="image-gallery"
>
<a
v-for="image in group.images"
:key="image.mediaItemUrl"
:href="image.mediaItemUrl"
class="image-gallery__link"
>
<img
:src="image.sourceUrl"
:alt="image.altText"
class="image-gallery__image"
>
</a>
</div>
</div>
</section>
</template>
<script>
export default {
name: 'ImageGallery',
props: {
group: {
type: Object,
required: true
}
},
mounted() {
let vm = this;
if (this.group && vm.$refs.lightgallery !== 'undefined') {
window.lightGallery(this.$refs.lightgallery, {
cssEasing: 'cubic-bezier(0.680, -0.550, 0.265, 1.550)'
});
}
}
}
</script>
<script>
import Choices from 'choices.js'
export default {
name: 'CountrySelect',
created () {
if(process.client) {
console.log(this.$refs, Choices)
const choices = new Choices(this.$refs.select)
console.log(choices)
}
}
}
</script>
I guess this should help, nuxt will touch insides of computed after it renders on server and window will be defined
This thread is a bit old, but I will leave my solution here so maybe someone finds it useful.
I had similar issue with vue-star-rating and few other plugins recently.
Below steps can be followed and adjusted depending on the plugin name, import / usage settings:
Go to your plugins folder and create new js file, in this case vue-star-rating.js, then edit it to setup the plugin:
import Vue from 'vue'
import VueStarRating from 'vue-star-rating'
Vue.component('vue-star-rating', VueStarRating); //<--- the name you used to register the plugin will be the same to use when in the component (vue-star-rating)
Go to your nuxt.config.js file and add plugin:
plugins: [{
src: '~/plugins/vue-star-rating', // <--- file name
mode: 'client'
},
//you can simply keep adding plugins like this:
{
src: '~/plugins/vue-slider-component',
mode: 'client'
}]
Now you are ready to use the plugin anywhere in the application. However, to do that you will need to wrap it in the container <client-only>. Example:
<client-only placeholder="loading...">
<vue-star-rating />
</client-only>
Notes:
You do not need to import anything locally to the component, simply using it like above should fix the problem.
Please make sure you are naming the plugin the same way in both places, step 1 and step 3. In this case it would be vue-star-rating.
if you still want to do it, document object can be taken this way:
const d = typeof document === 'undefined' ? null : document
For completeness, it's worth mentioning that instead of the object syntax in Yusuf Adeyemo answer (which I prefer as it separates out the file from how it is used), you can also set plugins to operate in client or server side only by naming the files like so:
export default {
plugins: [
'~/plugins/foo.client.js', // only in client side
'~/plugins/bar.server.js', // only in server side
'~/plugins/baz.js' // both client & server
]
}
src: https://nuxtjs.org/docs/directory-structure/plugins/#client-or-server-side-only
On top of all the answers here, you can also face some other packages that are not compatible with SSR out of the box (like in your case) and that will require some hacks to work properly. Here is my answer in details.
The TLDR is that you'll sometimes need to:
use process.client
use the <client-only> tag (be careful, it will not render but still execute the code inside)
use a dynamic import if needed later on, like const Ace = await import('ace-builds/src-noconflict/ace')
load a component conditionally components: { [process.client && 'VueEditor']: () => import('vue2-editor') }
With all of this, you're pretty much covered for every possible case.
I was trying to access document in created hook so when I moved the logic from created hook to mounted hook, my problem was solved.

Categories