storyblok / storyblok-nuxt-2

6 stars 8 forks source link

Live Preview editor not working: Uncaught SyntaxError: "undefined" is not valid JSON #62

Open studiolathe opened 2 years ago

studiolathe commented 2 years ago

Hi there

I am using storyblok-nuxt-2@^1.0.2 with Nuxt 2 (^2.15.8) and the Live Preview editor (V2) is no longer working (when it previously was). When inspecting the console inside the Storyblok editor I noticed I am receiving the below error on every click inside the preview:

Uncaught SyntaxError: "undefined" is not valid JSON
    at JSON.parse (<anonymous>)
    at storyblok-v2-latest.js:1:11649
    at m.handleOpenBlok (storyblok-v2-latest.js:1:11659)
    at m.handleWindowClick (storyblok-v2-latest.js:1:11405)

On inspection of the rendered component HTML elements and it seems I am getting undefined on the data-blok-c and data-blok-uid attributes on all of the Storyblok rendered components:

<div class="block-header-hero storyblok__outline" data-blok-c="undefined" data-blok-uid="undefined">
...
</div>

For further context, see the nuxt.config.js and page components:

nuxt.config.js
...
buildModules: [
    ["@storyblok/nuxt-2/module", {
      accessToken: process.env.STORYBLOK_PUBLIC_API_TOKEN,
      bridge: true,
      useApiClient: true,
      apiOptions: {
        cache: {
          clear: 'auto',
          type: 'memory'
        }
      },
    }],
    '@nuxtjs/composition-api/module',
  ],
build: {
    // ES Modules
    standalone: true,
    transpile: [
      '@nuxtjs/composition-api',
      '@storyblok/nuxt-2'
    ],
    extend(config) {
      config.module.rules.push({
        test: /\.(cjs|mjs)$/,
        exclude: {
          and: [/node_modules/],
          not: [/pathe/]
        },
        use: {
          loader: 'babel-loader',
          options: {
            presets: [['@babel/preset-env', { targets: 'ie 11' }]]
          }
        }
      })
    }
  }
/pages/index.vue
...
<template>
  <StoryblokComponent v-if="story" :blok="story.content" />
</template>

<script>
import { useStoryblokBridge, useStoryblokApi } from "@storyblok/nuxt-2";

export default {
  asyncData: async () => {
    const storyblokApi = useStoryblokApi();
    const { data } = await storyblokApi.get("cdn/stories/home", {
      version: "published",
    });

    return { story: data.story };
  },
  mounted() {
    useStoryblokBridge(this.story.id, (newStory) => (this.story = newStory));
  },
};
</script>
/components/storyblok/page.vue
...
<template>
  <div v-editable="blok" class="page">
    <StoryblokComponent
      v-for="blok in blok.body"
      :key="blok._uid"
      :blok="blok"
    />
  </div>
</template>

<script>
export default {
  props: {
    blok: {
      type: Object,
      required: true,
    },
  },
};
</script>

For additional context the preview was working and I have multiple projects using the same setup with no issues and inspecting the rendered elements displays the data attributes being rendered with Storyblok data..

Is this an issue related to my setup, the StoryblokComponent or with storyblok-v2-latest.js.

FYI I have tested with the latest storyblok-nuxt-2@^1.0.2 and the issues persists.

Any help would be greatly appreciated!

Thanks in advance.

studiolathe commented 2 years ago

@alexjoverm any ideas why this might be happening?

Dawntraoz commented 2 years ago

cc @manuelschroederdev @alexjoverm

alexjoverm commented 2 years ago

Very interesting issue @studiolathe! Judging by the error you're getting, seems like the issue it's at Storyblok Bridge level, so it's possible there is an issue there. That'd explain that you're also having the issue regardless on the SDK version. Is it possible for you to share something reproducible? A Stackblitz link would be very appreciated, so @Dawntraoz and I can investigate

studiolathe commented 2 years ago

Hi @alexjoverm

Thanks so much for getting back to me!

I have spent quite a bit of time stepping back through old commits to try and find if something changed that would be triggering this bug and I think I have found the culprit!

So... we were previously making all requests with the Preview access token and setting the request version to "Draft", see example below:

// pages/index.vue

asyncData: async () => {
    const storyblokApi = useStoryblokApi();
    const { data } = await storyblokApi.get("cdn/stories/home", {
      version: "draft",
    });

    return { story: data.story };
  },

But it seemed we facing some caching issues with our Netlify builds so we switched to using the Public access token and updated all requests to version: "published" and it looks like these settings are the culprit that breaks the Live Preview and triggers the error.

I have now reverted all requests to use version: "draft" and changed the main Storyblok config back to the Preview token and Live Preview seems to working as expected.

Do we know why the published version might be causing this and what is best practice for making in requests in a production site, should we use the Preview or Public token?

I have added the nuxt.config.js, package.json and index.vue so you can see the setup:

nuxt.config.js

import path from 'path'
import fs from 'fs'
// import axios from 'axios'

export default {
  // Target: https://go.nuxtjs.dev/config-target
  target: 'static',

  // Global page headers: https://go.nuxtjs.dev/config-head
  head: {
    title: 'XXXX',
    htmlAttrs: {
      lang: 'en'
    },
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: XXXX' },
      { name: 'format-detection', content: 'telephone=no' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
      { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' },
      { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' },
      { rel: 'icon', type: 'image/png', sizes: '192x192', href: '/android-chrome-192x192.png' },
      { rel: 'icon', type: 'image/png', sizes: '512x512', href: '/android-chrome-512x512.png' },
      { rel: 'icon', type: 'apple-touch-icon', href: '/apple-touch-icon.png' },
      { rel: 'manifest', href: '/site.webmanifest' }
    ],
    script: [
      {
        hid: "hs-script-loader",
        src: "//js.hs-scripts.com/XXXX.js",
        defer: true,
      },
    ],
  },

  loading: {
    color: '#64BE78',
    height: '2px',
    throttle: 0
  },

  // Global CSS: https://go.nuxtjs.dev/config-css
  css: [
    '@assets/scss/main.scss'
  ],

  // SCSS
  styleResources: {
    scss: [
      '@assets/scss/base/_02-mixins.scss',
      '@assets/scss/base/_03-variables.scss',
      '@assets/scss/base/_04-helpers.scss',
    ]
  },

  // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
  plugins: [
    { src: '@/plugins/vue-observe-visibility' },
    { src: '@/plugins/v-lazy-image' },
    { src: '@/plugins/vue-scrollactive', mode: 'client' },
    { src: '@/plugins/vuelidate' },
    { src: '@/plugins/lottie-vue-player.client.js' },
    { src: '@/plugins/google-gtag.client.js', mode: 'client' },
  ],

  // Auto import components: https://go.nuxtjs.dev/config-components
  components: true,

  // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
  buildModules: [
    '@nuxtjs/style-resources',
    '@nuxt/image',
    ["@storyblok/nuxt-2/module", {
      accessToken: process.env.STORYBLOK_PREVIEW_API_TOKEN,
      // bridge: true, Has no effect?
      // useApiClient: true, Has no effect?
      apiOptions: {
        cache: {
          type: "memory"
        }
      },
    }],
    '@nuxtjs/composition-api/module',
  ],

  // Modules: https://go.nuxtjs.dev/config-modules
  modules: [
    // https://go.nuxtjs.dev/axios
    '@nuxtjs/axios',
    '@nuxtjs/svg',
    '@nuxtjs/sitemap',
    '@nuxtjs/robots',
    [
      'nuxt-vuex-localstorage',
      {
        localStorage: ['cookies'],
        // expiration time in hours
        expire: 24 * 30,
      }
    ],
  ],

  // Nuxt Image: https://image.nuxtjs.org/providers/storyblok
  image: {
    storyblok: {
      baseURL: 'https://a.storyblok.com'
    }
  },

  // Public Runtime Config
  publicRuntimeConfig: {
    storyblokApiToken: process.env.STORYBLOK_PREVIEW_API_TOKEN,
    hubspotFormId: process.env.HUBSPOT_FORM_ID,
    hubspotPortalId: process.env.HUBSPOT_PORTAL_ID,
    g4Id: process.env.G4_ID
  },

  generate: {
      // Helps Netlify with 404 handling by generating a 404.html page
      fallback: true,

      // Exclude dynamic routes from static generation if using default Nuxt crawler
      // exclude: [
      //   /^\/global/
      // ],

      routes: function (callback) {
        const token = process.env.STORYBLOK_PREVIEW_API_TOKEN
        const version = 'published'
        const axios = require('axios');
        let cache_version = 0

        // Routes to exclude from static generation
        let toIgnore = ['home', 'posting', 'global', 'global/nav', 'global/menu', 'global/footer', 'global/error', 'global/cookies']

        // Other routes that are not in Storyblok with their slug.
        let routes = ['/'] // adds / directly

        // Load space and receive latest cache version key to improve performance
        axios.get(`https://api.storyblok.com/v1/cdn/spaces/me?token=${token}`).then((space_res) => {

          // timestamp of latest publish
          cache_version = space_res.data.space.version

          // Call for all Links using the Links API: https://www.storyblok.com/docs/Delivery-Api/Links
          axios.get(`https://api.storyblok.com/v1/cdn/links?token=${token}&version=${version}&cv=${cache_version}&per_page=100`).then((res) => {
            Object.keys(res.data.links).forEach((key) => {
              if (!toIgnore.includes(res.data.links[key].slug)) {
                routes.push('/' + res.data.links[key].slug)
              }
            })

            callback(null, routes)
          })
        })
      }
  },

  // Add trailing slash to our internal links in prod for Netlify (Storyblok preview won't resolve with trailing slash)
  router: {
    trailingSlash: process.env.NODE_ENV === 'development' ? undefined : true,
  },

  // Sitemap config
  sitemap: {
    hostname: 'https://www.example.com',
    defaults: {
      changefreq: 'monthly',
      priority: 1,
      trailingSlash: true
    },
    trailingSlash: true, 
  },

  // Robots.txt config
  robots: {
    UserAgent: '*',
    Disallow: () => (process.env.NODE_ENV === 'development') ? '/' : '' // Disallow everything in dev
  },

  // Server 
  server: {
    https: process.env.NODE_ENV === 'development' ? {
      key: fs.readFileSync(path.resolve(__dirname, 'key.pem')),
      cert: fs.readFileSync(path.resolve(__dirname, 'cert.pem'))
    } : false,
  },

  // Axios module configuration: https://go.nuxtjs.dev/config-axios
  axios: {
    // Workaround to avoid enforcing hard-coded localhost:3000: https://github.com/nuxt-community/axios-module/issues/308
    baseURL: '/',
  },

  // Build Configuration: https://go.nuxtjs.dev/config-build
  build: {
    // ES Modules
    standalone: true,
    transpile: [
      '@nuxtjs/composition-api',
      '@storyblok/nuxt-2'
    ],
    extend(config) {
      config.module.rules.push({
        test: /\.(cjs|mjs)$/,
        exclude: {
          and: [/node_modules/],
          not: [/pathe/]
        },
        use: {
          loader: 'babel-loader',
          options: {
            presets: [['@babel/preset-env', { targets: 'ie 11' }]]
          }
        }
      })
    }
  }
}

package.json (note this is working with "@storyblok/nuxt-2": "^1.1.0"

{
  "name": "XXXX",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "nuxt dev --https --ssl-cert localhost.pem --ssl-key localhost-key.pem",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate"
  },
  "dependencies": {
    "@nuxtjs/axios": "^5.13.6",
    "@nuxtjs/robots": "^2.5.0",
    "@nuxtjs/sitemap": "^2.4.0",
    "@nuxtjs/style-resources": "^1.2.1",
    "@nuxtjs/svg": "^0.4.0",
    "core-js": "^3.19.3",
    "intersection-observer": "^0.12.2",
    "nuxt": "^2.15.8",
    "nuxt-polyfill": "^1.0.3",
    "nuxt-vuex-localstorage": "^1.3.0",
    "swiper": "6.8.4",
    "v-lazy-image": "^1.4.0",
    "vue-awesome-swiper": "^5.0.1",
    "vue-gtag": "1.16.1",
    "vue-observe-visibility": "^1.0.0",
    "vue-scrollactive": "^0.9.3",
    "vue-select": "^3.20.0",
    "vue-server-renderer": "^2.6.14",
    "vue-template-compiler": "^2.6.14",
    "webpack": "^4.46.0"
  },
  "devDependencies": {
    "@lottiefiles/vue-lottie-player": "^1.0.9",
    "@nuxt/image": "^0.7.1",
    "@nuxtjs/composition-api": "^0.33.1",
    "@storyblok/nuxt-2": "^1.1.0",
    "sass": "^1.54.3",
    "sass-loader": "10",
    "vuelidate": "^0.7.7"
  }
}

pages/index.vue

<template>
  <StoryblokComponent v-if="story" :blok="story.content" />
</template>

<script>
import { useStoryblokBridge, useStoryblokApi } from "@storyblok/nuxt-2";

export default {
  asyncData: async () => {
    const storyblokApi = useStoryblokApi();
    const { data } = await storyblokApi.get("cdn/stories/home", {
      version: "draft",
      // version: "published" - This doesn't work with Public token
    });

    return { story: data.story };
  },
  computed: {
    seo() {
      if (this.story) {
        return this.story.content;
      }
    },
  },
  mounted() {
    useStoryblokBridge(this.story.id, (newStory) => (this.story = newStory));
  },
};
</script>

Please let me know your thoughts on best approach and if theres any improvements we can be making to the config?

Thanks in advance!

frans-vectra commented 1 year ago

Hi, any progress on this issue? I've got the same scenario where using the Public key causes the above mentioned error in the live preview. Using the Preview key for a production site isn't really an option for me.

Edit; Maybe I'm missing something with the configuration; should the Preview key somehow be used only when the project is rendered in the live preview window? I've got my access tokens defined in env variables, so they're pretty static once the project is deployed.

SebbeJohansson commented 1 year ago

@frans-vectra I am doing the same. I have the access token in env. I don't think there is a solution to have two different tokens so I guess the only option is to use the same key for both preview and live. It seems like the same applies for the nuxt3 module.

@Dawntraoz are we missing something? I had a discussion today with my team and the only option we have is to obscure all the request to a server api middleware that proxies the requests. Not ideal!

Dawntraoz commented 1 year ago

Hi folks @studiolathe, @frans-vectr, @SebbeJohansson, I think I may have found what is happening after double-checking your issue.

The idea behind the Preview and Public Token is that if you have a site for creating/previewing the content separated (deployed in another instance) from the production one, you will use Preview for the previewing environment and Public for the production one.

But as you're using the same site for previewing and publishing the content, then you will need to use the Preview Token and make a conditional like the following to change between draft and published if you're inside the editor or not:

asyncData: async (context) => {
    const storyblokApi = useStoryblokApi();

    // This is how we get that we are inside the Storyblok space:
    const version =
      context.query._storyblok || context.isDev ? 'draft' : 'published';

    const { data } = await storyblokApi.get("cdn/stories/home", {
      version,
    });

    return { story: data.story };
 },

I hope this clarifies what is happening. Let me know if it works in your cases and if you think it will be worth it to add it to the Readme πŸ’œ

SebbeJohansson commented 1 year ago

@Dawntraoz In my case, that doesn't fully fix our issue. Since we want to limit what actual Token is used, we would need two tokens inside the application.

Not sure if this is off-topic for this issue, but is there any documentation for handling "obscuring" the storyblok calls so that they are not shown on the client? Both for draft and for published content. If not, I've got something to figure out!

Dawntraoz commented 1 year ago

@SebbeJohansson I think you're right. That should not be part of this issue since the Visual Editor works.

Maybe it's better to open a "question" tag issue and we can discuss it there πŸš€

SebbeJohansson commented 1 year ago

@Dawntraoz Im not sure if you meant in the general repo, or on here. I made it here since I need a suggestion specifically for nuxt2 (since this is a work question πŸ˜‰). https://github.com/storyblok/storyblok-nuxt-2/issues/103

nathanielwarner commented 1 year ago

Has anyone had this problem occur for certain blocks on the page but not all of them? We are fetching the draft version of the story while preview mode is active. Indeed, the data-blok-c and data-blok-uid are undefined for these blocks.