Search code examples
javascripttypescriptvue.jsnuxt.jsserver-side-rendering

Nuxt failed Vue hydrate "Attempting to hydrate existing markup but container is empty. Performing full mount instead."


I'm trying to implement SSR in my project, but constantly getting this error/warn.

error/warn

How can I know what is wrong with my code?

I have a lot of components in my project, dont know if I need to show you all of my code, but there is main TypeScript

import {Book} from '@/data/data'

const useBooksStore = () => {


    const books = useState<Book[]>('books', () => [])
    const savedBooks = useState<String[]>('savedBooks', () => [])
    const isLoading = useState('isLoading', () => false)
    const apiKey = 'AIzaSyBz-gCuGyQopm_Ey2QWPrMGghy0D0e1FYY'

    const booksCookie = useCookie('savedBooks')


    //loading the list of books on query
    async function loadBooks(query: string) {

        const response = await fetch(`https://www.googleapis.com/books/v1/volumes?q=${query}&key=${apiKey}`)

        if (response.ok) {
            const result = await response.json()
            if (result.items) {

                books.value = books.value.filter(book => book.favourites === true)
                result.items.forEach(item => {
                    const existingBook = books.value.find(book => book.id === item.id)
                    if (!existingBook) {
                        let data = constructBookData(item)
                        books.value.unshift(data)
                    }
                })
            } else {
                books.value = books.value.filter(book => book.favourites === true)

                throw new Error('Not Found')
            }

        }
    }

    //loading saved books from cookies
    async function loadSavedBooks() {
        if (booksCookie.value) {
            savedBooks.value = booksCookie.value
        }
        let response, result
        for (const savedBook of savedBooks.value) {
            const existingBook = books.value.find(book => book.id === savedBook)
            if (!existingBook) {
                response = await fetch(`https://www.googleapis.com/books/v1/volumes/${savedBook}?key=${apiKey}`)
                if (response.ok) {
                    result = await response.json()
                    if (!books.value.find(book => book.id === result.id)) {
                        let data = constructBookData(result)
                        data.favourites = true
                        books.value.push(data)
                    }
                }
            } else {
                existingBook.favourites = true
            }
        }

    }

    //returns a new book object
    function constructBookData(result): Book {
        const data = {
            id: result.id,
            title: String(result.volumeInfo.title),
            imgUrl: result.volumeInfo.imageLinks && result.volumeInfo.imageLinks.thumbnail,
            shortDescr: result.volumeInfo.description,
            price: result.saleInfo.saleability === "FOR_SALE" ? Math.floor(result.saleInfo.retailPrice.amount) : result.saleInfo.saleability.split('_').join(' '),
            infoLink: result.volumeInfo.infoLink,
            favourites: false
        }
        return data

    }

    //handling loading (needed for loading spinner to show)
    async function handleLoading(callback) {
        isLoading.value = true
        try {
            await callback()
        } catch (err) {
            throw err
        } finally {
            isLoading.value = false
        }

    }

    //toggle favourite status of a book card
    function toggleFavourite(bookId: string) {
        const book = books.value.find(book => book.id === bookId)
        book.favourites = !book.favourites
        if (book.favourites && !savedBooks.value.find(book => book === bookId)) {
            savedBooks.value.push(book.id)
        } else {
            savedBooks.value.splice(savedBooks.value.findIndex(book => book === bookId), 1)
        }
        booksCookie.value = JSON.stringify(savedBooks.value)
    }

    //returns a list of saved books
    function getSavedBooks() {
        if (books.value) {
            return books.value.filter(book => book.favourites === true)
        }
    }

    //returns loading status
    function getLoading() {
        return isLoading.value
    }

    return {
        books,
        loadBooks,
        getSavedBooks,
        toggleFavourite,
        loadSavedBooks,
        handleLoading,
        getLoading
    }

}
export default useBooksStore

My main question is how can I know whats wrong with my code to solve this problem?


Solution

  • I found an answer. The issue was in this code

    <component :is="type === 'link' ? 'a' :
        (type === 'button' && 'button')" :href='href' class="btn" @click="emit('click')" :class="class">
         <slot/>
       </component>
    

    So I think dont use this type of component rendering, because it is not SSR friendly a guess) I rewrite it to this code

     <a v-if="type === 'link'" :href="href" class="btn" @click="emit('click')">
         <slot/>
       </a>
       <button v-else-if="type === 'button'" :href="href" class="btn" @click="emit('click')">
         <slot/>
       </button>