DEV Community

Jeremy Wynn
Jeremy Wynn

Posted on • Originally published at jeremywynn.com on

A crazy adventure with CORS, Nuxt, and Webmentions

The goal didn't sound daunting in and of itself: to incorporate the like portion of webmentions 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 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.

Getting them on my site

Now there was the matter of implementation. There were a few options such as Javascript plugins webmention.js and embeds such as A Webmention Endpoint. Then there were APIs -- webmention.io even has its own API where I could retrieve a list of webmentions like 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 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
    }; 
  } 
}, 
Enter fullscreen mode Exit fullscreen mode

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:

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

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 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 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' 
  } 
}, 
Enter fullscreen mode Exit fullscreen mode

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:

⚠ Does not work in generated/static mode!

**#^%!*

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. 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
  5. Wait for something like Warpist
  6. Cry?

Why can't this shit just work?

The answer: Middleware

I am not entirely sure how right now, but using 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); 
} 
Enter fullscreen mode Exit fullscreen mode

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); 
  } 
}; 
Enter fullscreen mode Exit fullscreen mode

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; 
  } 
}, 
Enter fullscreen mode Exit fullscreen mode

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.

Latest comments (1)

Collapse
 
panvakalo profile image
Panos Vakalopoulos

I am really wondering how this solved your CORS issue.

I faced the exact problem as you did, thought that I saved the day (or the week) by using @nuxt/proxy, but my CI/CD pipeline was already configured for SPA mode.

I tried using interceptor or any kind of middleware, but nope. the CORS issue still remained. :/