Answers.Body: <blockquote>
  <p><strong>Update:</strong><br />
  As of Craft 3.2 there is native support for headless previews. Please
  see <a href="https://craftcms.stackexchange.com/a/31039/7275">Cue's extensive answer</a> for a detailed
  explanation.</p>
  </blockquote>
  <br>
  <h3>Original answer</h3>
  <p>I managed to do this, although I'm still not quite happy with it.<br />
  First part of the solution is figuring out whether this is a live preview. To do so, I decided to append data attributes to the HTML element. You're free to use global variables or whatever else suits you, though.</p>
  <pre class="lang-html prettyprint-override"><code>{% if craft.request.isLivePreview %}
    &lt;script type=&quot;text/javascript&quot;&gt;
      window.document.documentElement.dataset.livePreview = true;
      window.document.documentElement.dataset.path        = '{{ '/' ~ craft.request.path }}';
    &lt;/script&gt;
  {% endif %}
  </code></pre>
  <p>Next up, getting Vue-Router to not use <code>window.location</code> for route matching and navigating straight to the desired URI:</p>
  <pre class="lang-js prettyprint-override"><code>const isLivePreview  = window.document.documentElement.dataset.livePreview,
        actualPath     = window.document.documentElement.dataset.path;
  
  if (isLivePreview) {
    routerConfig.mode = 'abstract';
  }
  
  const router = new VueRouter(routerConfig);
  
  if (isLivePreview) {
    router.replace({ path: actualPath });
  }
  </code></pre>
  <p>This makes live preview work like a charm so far. What I don't like, though, is a) the doubled <code>if (isLivePreview)</code> and b) the need to check this for every visitor and therefore 99% of all requests.<br />
  If anyone can come up with something better it will be greatly appreciated. I'm leaving the question open for a few days.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2017-09-20T06:18:19.857Z
Answers.LastActivityDate: 2019-08-21T10:18:51.200Z
Answers.LastEditDate: 2020-06-17T08:56:27.903Z
Answers.UserDisplayName: Moritz Friedrich
Answers.Body: <p><a href="https://craftcms.com/blog/craft-32" rel="noreferrer">Craft 3.2</a> introduces Headless Preview which works a treat with SPA's. Essentially Craft exposes <a href="https://docs.craftcms.com/api/v3/craft-web-request.html#method-gettoken" rel="noreferrer">tokens</a> which you can forward on to subsequent Element API requests and Craft will do the rest. The interesting thing to note with this release is that Live Preview isn't actually "live" rather draft entries that are saved and accessible by tokens generated to identify the entry version.</p>
  
  <p>Your example outlined above is adequate in configuring <a href="https://router.vuejs.org/" rel="noreferrer">Vue Router</a> for these previews to work. But we also need to pass on these preview tokens to Element API. These tokens allow Craft to do away with obfuscating URL's loaded in the Live Preview iframe and now load the actual entry URL as you would access it on the actual website.</p>
  
  <p>For example, previously Live Preview would load <code>/admin/entries/282-project-slug</code> in an iframe, it now loads the entry target URL <code>/projects/project-slug?x-craft-preview=XXX&amp;token=XXX</code>. Fantastic!</p>
  
  <hr>
  
  <p><strong>Detecting Preview Mode</strong></p>
  
  <p>Live Preview loads the entry URL in an iframe with tokens exposed via a query string. These are <code>x-craft-preview</code> and <code>token</code>. We can extract these from the URL either client-side or server-side. The choice is entirely up to you but we just need to grant easy access to these tokens.</p>
  
  <p>Client-side detection example:</p>
  
  <pre><code>
  let hashes = window.location.search.slice(window.location.search.indexOf('?') + 1).split('&amp;')
  let params = {}
  hashes.map(hash =&gt; {
    let [key, val] = hash.split('=')
    params[key] = decodeURIComponent(val)
  })
  
  // x-craft-preview = params['x-craft-preview']
  // token = params['token']
  </code></pre>
  
  <p>Server-side detection example (Twig):</p>
  
  <pre><code>{% if craft.app.request.isPreview %}
     token = {{ craft.app.request.getQueryParam('token') }}
     x-craft-preview = {{ craft.app.request.getQueryParam('x-craft-preview') }}
  {% endif %}
  </code></pre>
  
  <blockquote>
    <p>It's worth noting that Craft 3.2 also deprecates <code>isLivePreview</code> in
    favour of <code>isPreview</code> since Live Preview is no longer <em>programatically
    live</em> rather drafts saved on-the-go and tokenized.</p>
  </blockquote>
  
  <hr>
  
  <p><strong>Configuring Vue</strong></p>
  
  <p>For demonstration purposes we will declare the following variables and assume we now have access to the tokens.</p>
  
  <pre><code>const craftPreview = // value of 'x-craft-preview',
  const craftToken = // value of 'token'
  </code></pre>
  
  <p>Set the router mode to <code>mode: 'abstract'</code>, for example:</p>
  
  <pre><code>const router = new VueRouter({
    mode: craftPreview ? 'abstract' : 'history',
    ...
  })
  </code></pre>
  
  <p>The router will resolve the preview URL so there's no need to replace the path. If you're still having trouble with this then you can try setting it to see if it resolves your problem...</p>
  
  <pre><code>router.replace({ path: window.location.pathname })
  </code></pre>
  
  <p>Below is an example of forwarding these params with Axios as our HTTP client.</p>
  
  <pre><code>axios.get('/api/v1/project.json', {
    params: {
      slug: 'project-slug',
      token: craftToken,
      'x-craft-preview': craftPreview
    }
  })
  </code></pre>
  
  <p>Alternatively, you could utilise Axios interceptors and configure params before each request.</p>
  
  <pre><code>axios.interceptors.request.use(
    (config) =&gt; {
      // Forward Craft Preview tokens to API
      if (craftPreview) {
        const params = {
          token: craftToken,
          'x-craft-preview': craftPreview
        }
        if (!config.params) {
          config.params = params
        }
        else {
          Object.keys(params).map(key =&gt; config.params[key] = params[key])
        }
      }
      return config
    },
    (err) =&gt; {
      return Promise.reject(err)
    }
  )
  </code></pre>
  
  <p>Hopefully this steers people in the right direction. As always, huge shout out to the Craft team for making this possible.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2019-08-13T04:33:23.560Z
Answers.Text: I wrote the [Headless Preview in Craft CMS](https://nystudio107.com/blog/headless-preview-in-craft-cms) article that goes a bit deeper under the hood in terms of how it works in Craft-land.  I linked to your post here.
Answers.UserDisplayName: andrew.welch
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2019-07-21T17:27:12.193Z
Answers.LastActivityDate: 2019-07-21T17:34:45.140Z
Answers.LastEditDate: 2019-07-21T17:34:45.140Z
Answers.UserDisplayName: Cue
Body: <p>I'm building a Vue.js site with Craft as the backend, which works pretty well so far. The only thing I can't get to work yet is live preview.<br>
  Vue-Router relies on the <code>window.location</code> which is flawed in live preview (<code>https://example.com/admin/entries/pages/1-homepage</code> instead of <code>https://example.com/homepage</code>). Is there any way to get this right?</p>
  
  <p><strong>Update: A new issue popped up.</strong> About to test things, I noticed the content does not change because - obviously - I pull it from the Element API, which pulls it from the database. I'll update my answer below when I've found a way around this.</p>
  
Tags: <frontend><live-preview>
Title: Live preview using Vue.js
ThreadText: # Live preview using Vue.js
  
  (Asked by: username_0 on 2017-09-19)
  
  I'm building a Vue.js site with Craft as the backend, which works pretty well so far. The only thing I can't get to work yet is live preview.  
  Vue-Router relies on the `window.location` which is flawed in live preview (`https://example.com/admin/entries/pages/1-homepage` instead of `https://example.com/homepage`). Is there any way to get this right?
  
  **Update: A new issue popped up.** About to test things, I noticed the content does not change because - obviously - I pull it from the Element API, which pulls it from the database. I'll update my answer below when I've found a way around this.
  
  **Tags:** frontend, live-preview
  
  
  ---
  
  ## 2 answers
  
  ### Answer by username_1 on 2019-07-21. Score: 6 | Accepted answer
  
  [Craft 3.2](https://craftcms.com/blog/craft-32) introduces Headless Preview which works a treat with SPA's. Essentially Craft exposes [tokens](https://docs.craftcms.com/api/v3/craft-web-request.html#method-gettoken) which you can forward on to subsequent Element API requests and Craft will do the rest. The interesting thing to note with this release is that Live Preview isn't actually "live" rather draft entries that are saved and accessible by tokens generated to identify the entry version.
  
  Your example outlined above is adequate in configuring [Vue Router](https://router.vuejs.org/) for these previews to work. But we also need to pass on these preview tokens to Element API. These tokens allow Craft to do away with obfuscating URL's loaded in the Live Preview iframe and now load the actual entry URL as you would access it on the actual website.
  
  For example, previously Live Preview would load `/admin/entries/282-project-slug` in an iframe, it now loads the entry target URL `/projects/project-slug?x-craft-preview=XXX&token=XXX`. Fantastic!
  
  ---
  
  **Detecting Preview Mode**
  
  Live Preview loads the entry URL in an iframe with tokens exposed via a query string. These are `x-craft-preview` and `token`. We can extract these from the URL either client-side or server-side. The choice is entirely up to you but we just need to grant easy access to these tokens.
  
  Client-side detection example:
  
  ```
  
  let hashes = window.location.search.slice(window.location.search.indexOf('?') + 1).split('&')
  let params = {}
  hashes.map(hash => {
    let [key, val] = hash.split('=')
    params[key] = decodeURIComponent(val)
  })
  
  // x-craft-preview = params['x-craft-preview']
  // token = params['token']
  
  ```
  
  Server-side detection example (Twig):
  
  ```
  {% if craft.app.request.isPreview %}
     token = {{ craft.app.request.getQueryParam('token') }}
     x-craft-preview = {{ craft.app.request.getQueryParam('x-craft-preview') }}
  {% endif %}
  
  ```
  
  > It's worth noting that Craft 3.2 also deprecates `isLivePreview` in
  > favour of `isPreview` since Live Preview is no longer *programatically
  > live* rather drafts saved on-the-go and tokenized.
  
  ---
  
  **Configuring Vue**
  
  For demonstration purposes we will declare the following variables and assume we now have access to the tokens.
  
  ```
  const craftPreview = // value of 'x-craft-preview',
  const craftToken = // value of 'token'
  
  ```
  
  Set the router mode to `mode: 'abstract'`, for example:
  
  ```
  const router = new VueRouter({
    mode: craftPreview ? 'abstract' : 'history',
    ...
  })
  
  ```
  
  The router will resolve the preview URL so there's no need to replace the path. If you're still having trouble with this then you can try setting it to see if it resolves your problem...
  
  ```
  router.replace({ path: window.location.pathname })
  
  ```
  
  Below is an example of forwarding these params with Axios as our HTTP client.
  
  ```
  axios.get('/api/v1/project.json', {
    params: {
      slug: 'project-slug',
      token: craftToken,
      'x-craft-preview': craftPreview
    }
  })
  
  ```
  
  Alternatively, you could utilise Axios interceptors and configure params before each request.
  
  ```
  axios.interceptors.request.use(
    (config) => {
      // Forward Craft Preview tokens to API
      if (craftPreview) {
        const params = {
          token: craftToken,
          'x-craft-preview': craftPreview
        }
        if (!config.params) {
          config.params = params
        }
        else {
          Object.keys(params).map(key => config.params[key] = params[key])
        }
      }
      return config
    },
    (err) => {
      return Promise.reject(err)
    }
  )
  
  ```
  
  Hopefully this steers people in the right direction. As always, huge shout out to the Craft team for making this possible.
  
  > **Comments:**
  > **username_2**: I wrote the [Headless Preview in Craft CMS](https://nystudio107.com/blog/headless-preview-in-craft-cms) article that goes a bit deeper under the hood in terms of how it works in Craft-land.  I linked to your post here.  
  
  ---
  
  ### Answer by username_0 on 2017-09-20. Score: 3
  
  > **Update:**  
  > As of Craft 3.2 there is native support for headless previews. Please
  > see [Cue's extensive answer](https://craftcms.stackexchange.com/a/31039/7275) for a detailed
  > explanation.
  
    
  
  ### Original answer
  
  I managed to do this, although I'm still not quite happy with it.  
  First part of the solution is figuring out whether this is a live preview. To do so, I decided to append data attributes to the HTML element. You're free to use global variables or whatever else suits you, though.
  
  ```html
  {% if craft.request.isLivePreview %}
    <script type="text/javascript">
      window.document.documentElement.dataset.livePreview = true;
      window.document.documentElement.dataset.path        = '{{ '/' ~ craft.request.path }}';
    </script>
  {% endif %}
  
  ```
  
  Next up, getting Vue-Router to not use `window.location` for route matching and navigating straight to the desired URI:
  
  ```js
  const isLivePreview  = window.document.documentElement.dataset.livePreview,
        actualPath     = window.document.documentElement.dataset.path;
  
  if (isLivePreview) {
    routerConfig.mode = 'abstract';
  }
  
  const router = new VueRouter(routerConfig);
  
  if (isLivePreview) {
    router.replace({ path: actualPath });
  }
  
  ```
  
  This makes live preview work like a charm so far. What I don't like, though, is a) the doubled `if (isLivePreview)` and b) the need to check this for every visitor and therefore 99% of all requests.  
  If anyone can come up with something better it will be greatly appreciated. I'm leaving the question open for a few days.
  
  
  ---
  
  