My page has a header, that I get from file located in server:
data() {
return {
headerHTML: ''
};
},
fetch() {
this.headerHTML = fs.readFileSync('./header.html', 'utf8');
}
I render header using v-html:
<div v-html="headerHTML"></div>
<div class="container">
<Nuxt />
</div>
It works fine, but header is beeing sent from server to client twice - 1st time in HTML generated in server-side rendering process and 2nd time as a data "headerHTML" in Vue component. Header is large, so it affects performance.
Is there a way to render HTML only on server, wihout sending data to client?
AFAIK, there is no way to have it rendered only on the server because it's the main purpose of Nuxt (being isomorphic: server + client).
There is probably some other way to make it a bit more performance efficient.
I've figured it out.
I've created new Header component:
export default {
render(createElement: CreateElement) {
if (process.server) {
const fs = process.server ? require('fs') : null;
const file = fs.readFileSync('./header.txt', 'utf8');
return createElement('div', { domProps: { innerHTML: f } });
}
return createElement('div', { domProps: { innerHTML: this.$el.innerHTML } });
},
}
And I use it instead v-html in main component:
<Header />
<div class="container">
<Nuxt />
</div>
In this way on the server side Nuxt loads file to variable, set html using innerHTML and returns the page to client (without header content stored in component data).
On the client side Header component render what is already inside it.
Related
I'm making a vue js app using the CDN like this:
<div id="bookingApp">
<select-service />
<div>
Hello there
</div>
</div>
... and in js ...
const SelectService = {
template: '<h1>In Here!!</h1>',
setup() {
}
}
const bookingApp = Vue.createApp({
components: { "select-service": SelectService },
data() {
return {
sid: -1,
rid: -1
}
}
});
bookingApp.mount('#bookingApp');
... However, in #bookingApp it only shows "In Here!!" and not "Hello there" below it.
If I remove the then it shows "Hello there" as expected.
Why is it that I can't have component and still show the Hello there as well?
You should not use self-closing tags for Vue components in DOM templates as HTML only allows self closing for well known types like <input> and <img>.
For more details see the documentation:
https://vuejs.org/guide/essentials/component-basics.html#dom-template-parsing-caveats
I am not very good at English, I will describe the problem as clearly as possible.
<template>
<div class="Index">
<div
class="content"
v-html="content"
></div>
</div>
</template>
export default {
data() {
return {
content: '',
}
},
mounted() {
// This data is returned by axios,This is rich text with lots of tags, including script
this.content="<p>1111</p><div>222<div><script>alert() //There is a lot of code here.</script>"
}
}
The console will report this error:
Syntax Error: SyntaxError: /Users/sinx/development/cxz/src/views/Index.vue: Unterminated template (33:19)
Now, the problem is how to insert the data returned by axios into vue using v-html. Thank you all.
The initial judgment seems to be due to the reason of <script>
Make sure to include your js code inside scripts tags, like below;
<script> include </script>
That should fix your problem.
you should put all your JS in the script tag. The basic structure of a Vue app is like this.
<template>
//Your html here
</template>
<script>
export default {
data(){
return {
content : ""
}
},
mounted(){
axios.get(url).then(response => {
this.content = response.data //depends on the structure of response
}).then(error => {
console.log(error);
});
}
}
<script>
Hope this helps :)
Find the problem, if you want to render rich text, especially rich text with a srcipt tag, the tag needs to be escaped.
But,Although v-html can render tags, but can't execute the contents of the script, if I want to know how to execute the contents of the script, I have a way.
Use axios...
axios.get function uses a Promise.
When the API returns data successfully, the code within the then block is executed.
mounted() {
axios.get(url).then(response => {
this.results = response.data
})
}
Here is the link
Using Axios to Consume APIs
from official site.
I'm trying to solve this for Nuxt
Codesandbox of a WIP not working: https://codesandbox.io/s/zw26v3940m
OK, so I have WordPress as a CMS, and it's outputting a bunch of HTML. A sample of the HTML looks like this:
'<h2>A heading tag</h2>
<site-banner image="{}" id="123">Slot text here</site-banner>
<p>some text</p>'
Notice that it contains a Vue component <site-banner> that has some props on it (the image prop is a JSON object I left out for brevity). That component is registered globally.
I have a component that we wrote, called <wp-content> that works great in Vue, but doesn't work in Nuxt. Note the two render functions, one is for Vue the other is for Nuxt (obviously this is for examples sake, I wouldn't use both).
export default {
props: {
html: {
type: String,
default: ""
}
},
render(h, context) {
// Worked great in Vue
return h({ template: this.html })
}
render(createElement, context) {
// Kind of works in Nuxt, but doesn't render Vue components at all
return createElement("div", { domProps: { innerHTML: this.html } })
}
}
So the last render function works in Nuxt except it won't actually render the Vue components in this.html, it just puts them on the page as HTML.
So how do I do this in Nuxt? I want to take a string of HTML from the server, and render it on the page, and turn any registered Vue components into proper full-blown Vue components. Basically a little "VueifyThis(html)" factory.
This was what worked and was the cleanest, thanks to Jonas Galvez from the Nuxt team via oTechie.
export default {
props: {
html: {
type: String,
default: ""
}
},
render(h) {
return h({
template: `<div>${this.html}</div>`
});
}
};
Then in your nuxt.config.js file:
build: {
extend(config, ctx) {
// Include the compiler version of Vue so that <component-name> works
config.resolve.alias["vue$"] = "vue/dist/vue.esm.js"
}
}
And if you use the v-html directive to render the html?
like:
<div v-html="html"></div>
I think it will do the job.
Here's a solution on codesandbox: https://codesandbox.io/s/wpcontent-j43sp
The main point is to wrap the dynamic component in a <div> (so an HTML tag) in the dynamicComponent() template, as it can only have one root element, and as it comes from Wordpress the source string itself can have any number of top level elements.
And the WpContent component had to be imported.
This is how I did it with Nuxt 3 :
<script setup lang="ts">
import { h } from 'vue';
const props = defineProps<{
class: string;
HTML: string
}>();
const VNode = () => h('div', { class: props.class, innerHTML: props.HTML })
</script>
<template>
<VNode />
</template>
There was not need to update nuxt.config.ts.
Hopefully it will help some of you.
I made some changes to your codesandbox. seems work now https://codesandbox.io/s/q9wl8ry6q9
Things I changed that didn't work:
template can only has one single root element in current version of Vue
v-bind only accept variables but you pass in a string.
I have a bunch of entities I want to display on a page in a Vue 3 application. Each entity should have an image URL. If an entity does not have such a URL, I want to display a default image that's stored in /src/assets/images/default.png
So I've created a computed property that returns the image URL, either entity.url or the URL above.
Unfortunately, this doesn't work, instead of getting the default image I'm getting no image, and the Vue dev webserver returns index.html. This is because Vue's handling of static assets doesn't work when the static asset URL is returned in runtime.
The image is shown properly if I hard-code the URL #/assets/images/default.png, but not if I return this as a string (dropping the # makes no difference).
How can I make Vue handle static assets that are referenced dynamically?
I'd use v-if/v-else to show the appropriate image based on whether entity.url exists ..
<img v-if="entity.url" :src="entity.url" />
<img v-else src="#/assets/images/default.png" />
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
imageData:{ imgUrl:'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/Wiktionary_small.svg/350px-Wiktionary_small.svg.png',
}
},
methods: {
getUrlData(data) {
if (data && data.imgUrl) {
return data.imgUrl
}
else {
return "#/assets/images/default.png"
}
}
}
})
<div id="app">
<p>{{ message }}</p>
<img :src="getUrlData(imageData)">
</div>
Try this should work in your case! and let me know if you are getting any errors
In Vue 3, you can put an #error event on the image, so if the URL returns a 404, you can execute arbitrary logic:
<script setup>
import { ref } from 'vue';
const isProfileImageBroken = ref(false);
const handleBrokenImage = () => {
console.log('image broke');
isProfileImageBroken.value = true;
};
</script>
<template>
<i v-if="!auth.user.image || isProfileImageBroken" class="icon-user icon-lg"></i>
<img
v-if="auth.user.image && !isProfileImageBroken"
:src="auth.user.image"
class="w-6 h-6 rounded-full object-cover"
#error="handleBrokenImage"
>
</template>
I'm using this plugin to create a portfolio gallery on a single page components application but I'm stuck loading the images from a relative path, it works fine if I use a full URL path.
When I use the full URL the image Loads and loose the blur as expected, instead when I use a relative path the image load but it keeps blurred and it doesn't load the class .v-lazy-image-loaded into the image tag. Any Ideas? here is the code.
LazyImage.vue
<template>
<v-lazy-image :src="src" />
</template>
<script>
import VLazyImage from 'v-lazy-image'
export default {
props: {
// HERE I TRIED TO USE A FUNCTION ALSO WITH NO LUCK
// src: Function
src: String
},
components: {
VLazyImage
},
// UPDATE: I added a method to test here.
methods: {
consoleSuccess (msg) {
console.log(msg)
}
}
}
</script>
Portfolio.vue
<template>
<section id="portfolio">
// I'M TRYING THIS TWO OPTIONS
// UPDATED: I Added here the events calls and its just calling the intersect but the load.
<v-lazy-image src="../assets/img/innovation.jpg" alt="alternate text" #load="consoleSuccess('LOADED!')" #intersect="consoleSuccess('INTERSECS!')"></v-lazy-image> <-- this just work with full url ex. http://cdn...
<v-lazy-image :src="require('../assets/img/innovation.jpg')" alt="alternate text"></v-lazy-image> <-- this loads the image but remains blured
</section>
</template>
<script>
import VLazyImage from '#/components/lazyImage'
export default {
components: {
VLazyImage
}
}
</script>
UPDATE
I added a method to test and realized that just the #intersect event it's called and the #load won't fire at all with relative paths.
I noticed this was caused by bug in the component code:
if (this.$el.src === this.src) {
should be replaced by
if (this.$el.getAttribute('src') === this.src) {
I've sent pull request for this.