<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>

CC BY-SA 4.0
2017-09-20T06:18:19.857Z
2019-08-21T10:18:51.200Z
2020-06-17T08:56:27.903Z
Moritz Friedrich
<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>

CC BY-SA 4.0
2019-08-13T04:33:23.560Z
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.
andrew.welch
CC BY-SA 4.0
2019-07-21T17:27:12.193Z
2019-07-21T17:34:45.140Z
2019-07-21T17:34:45.140Z
Cue
<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>

<frontend><live-preview>
Live preview using Vue.js
# 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 introduces Headless Preview which works a treat with SPA's. Essentially Craft exposes tokens 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 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 username_1's extensive answer 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.
{% if craft.request.isLivePreview %}
  
{% endif %}

Next up, getting Vue-Router to not use window.location for route matching and navigating straight to the desired URI:
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.


---

