A crazy adventure with CORS, Nuxt, and Webmentions
Tags: vue, nuxt, cors, webmentions
Published: 2019-09-26 17:40:35

---
title: A crazy adventure with CORS, Nuxt, and Webmentions
published: true
tags: Vue, Nuxt, CORS, Webmentions
canonical_url: 
---

The goal didn't sound daunting in and of itself: to incorporate the like portion of [webmentions](https://webmention.net/) on my single blog post pages. I've seen them in various places and even found an article by Remy Sharp called [Send Outgoing Webmentions](https://remysharp.com/2019/06/18/send-outgoing-webmentions) that explains how to do the setup required for receiving webmentions along with supplemental links. I finished all the prerequisites, and I was able to see my webmentions after logging into [Webmention.io](https://webmention.io/). 

## Getting them on my site 

Now there was the matter of implementation. There were a few options such as Javascript plugins [webmention.js](https://github.com/resonance-cascade/webmention.js) and embeds such as [A Webmention Endpoint](https://webmention.herokuapp.com/). Then there were APIs -- webmention.io even has its own [API](https://github.com/aaronpk/webmention.io#api) where I could retrieve a list of webmentions like [https://webmention.io/api/mentions.jf2?target=https://webmention.io](https://webmention.io/api/mentions.jf2?target=https://webmention.io). That API gave me JSON, I could easily do something with that, lots of things. 

## Implementation with Nuxt 

After I placed the necessary elements in the `<head>` and set up my Vue template to utilize the JSON, the next task was to get this JSON to my site. I was already using the [asyncData method](https://nuxtjs.org/guide/async-data/) to get the JSON of my blog post (which come from physical files), so I tried to add getting the webmention JSON here too as follows: 

``` 
async asyncData({ $axios, params, payload, route }) { 
  const token = process.env.webmentionsToken; 
  let likes = null; 
  try { 
    likes = await $axios.$get('https://webmention.io/api/mentions.jf2', { 
      params: { 
        target: 'https://jeremywynn.com' + route.fullPath + '/', 
        token: process.env.webmentionsToken, 
        'wm-property': 'like-of', 
        'per_page': 20 
      } 
    }); 
  } catch(error) { 
    console.log(error); 
  } 
  if (payload) { 
    return { blogPost: payload, likes}; 
  } else { 
    return { 
      blogPost: await import(`~/assets/content/blog/${params.blog}.json`), likes
    }; 
  } 
}, 
``` 
It worked! 

## Not so fast 

I noticed that it was working whenever I loaded or refreshed the page, but the JSON wasn't loading when I clicked through my site. I looked at the Firefox Dev Tools Console and found this error: 

<blockquote>Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://webmention.io/api/mentions.jf2?target=https:%2F%2Fje…token=blahblahblahblah&wm-property=like-of&per_page=20. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).</blockquote>

Chrome, Internet Explorer, and Opera seemed fine with it though. It was Firefox and Safari that had this problem involving CORS. I noticed in the Firefox Network panel that the request was being made with the `OPTIONS` method while Chrome always used `GET`. The `OPTIONS` method is how a [preflight request](https://www.html5rocks.com/en/tutorials/cors/) is made, but it seemed that the API server of webmention.io does not include the necessary elements in its Preflight Response that Firefox or Safari wanted (like `Access-Control-Allow-Origin`). 

I needed `application/json`, and there was no way to make this a simple HTTP request that would make Firefox/Safari not use the `OPTIONS` method. 

## I thought I knew what to do 

The sort of weird behavior was occuring because `asyncData` is called server-side once (on the first request to the Nuxt app) and client-side when navigating to further routes (webmentions were showing up in Firefox/Safari when I manually refreshed the page). I knew that the [@nuxtjs/proxy](https://github.com/nuxt-community/proxy-module#readme) can be used to make external requests look like it came from your own site. 

I had this. So I made this update to `nuxt.config.js`: 

``` 
modules: [
  '@nuxtjs/axios', 
  '@nuxtjs/proxy'
], 
proxy: { 
  '/api/mentions.jf2': { 
    target: 'https://webmention.io' 
  } 
}, 
``` 

After updating my axios call under the asyncData area in my component to remove the `https://webmention.io/` portion, I clicked around my site and everything was working everywhere! Gleefully, I pushed all the updates (after a lot of research and work) to Netlify, but then I noticed this critical @nuxtjs/proxy caveat:

<blockquote>⚠ Does not work in generated/static mode!</blockquote> 

***#^%!** 

## What else could be done 

I did not have control over the webmention.io API server. The CORS problem is not the fault of axios. Passing the axios call whatever configuration options I found in desperation did nothing. There were at least a few other options: 

1. Use JSONP: The webmention.io API does support it with the inclusion of the `jsonp` parameter. It has been used to bypass cross-origin sharing pain in the past. 
2. Use another API such as [A Webmention Endpoint](https://webmention.herokuapp.com/documentation.html). Maybe this server would handle the `OPTIONS` method from requests differently. 
3. Use Javascript or HTML embed methods mentioned earlier 
4. Use and host my own instance of [CORS Anywhere](https://github.com/Rob--W/cors-anywhere/) 
5. Wait for something like [Warpist](https://warpist.com/) 
6. Cry? 

**Why can't this shit just work?** 

## The answer: Middleware 

I am not entirely sure how right now, but using [middleware](https://nuxtjs.org/api/pages-middleware/) works since it makes the requests to the webmention.io API always utilize `GET` even in Firefox/Safari. 

In `middleware/webmention.js`: 

``` 

import axios from 'axios' 

export default async function ({ route, store }) {
  const likes = await axios.get('https://webmention.io/api/mentions.jf2', {
    params: { 
      target: 'https://jeremywynn.com' + route.fullPath + '/', 
      token: process.env.webmentionsToken, 
      'wm-property': 'like-of', 
      'per_page': 20 
    } 
  }); 
  store.dispatch('setWebMentions', likes.data); 
} 
``` 

Vuex store is how I am delivering this webmention JSON for my site. In `store/index.js`: 

``` 
export const state = () => ({ 
  webmentions: null 
}); 

export const mutations = { 
  SET_WEB_MENTIONS(state, webmentions) { 
    state.webmentions = webmentions; 
  } 
}; 

export const actions = { 
  setWebMentions({ commit }, webmentions) { 
    commit('SET_WEB_MENTIONS', webmentions); 
  } 
}; 
``` 

In my page component `.vue` file: 

Things will be handled by the middleware now, so I removed the axios and likes related code from `asyncData`. I added a `computed` entry for likes to get them from the store: 

``` 
computed: { 
  likes() { 
    return this.$store.state.webmentions; 
  } 
}, 
``` 

and made sure to call the middleware in the component: 

`middleware: 'webmention',` 

Now I can click around and have webmentions load correctly without any CORS issues.