title: How do you use routes from a headless CMS in Nuxt JS (SSR)?
body: <h1>The Question</h1>
  <p>I'm trying to use the routes, as defined by the CMS, to fetch content, which is used to determine which Page to load in my our Nuxt frontend.  So, how do you implement your own logic for connecting routes to content, and content to pages while maintaining all of the awesome features of a Nuxt Page?</p>
  <p>Any help would be greatly appreciated!</p>
  <h1>The Context</h1>
  <p>Headless CMSs are on the rise and we'd like to use ours with Nuxt in Universal mode (SSR).</p>
  <p>We are tied into using Umbraco (a free-to-host CMS), which has a flexible routing system that we can't restrict without massive user backlash.</p>
  <blockquote>
  <p>Note: Umbraco is not headless, we're adding that functionality ourselves with the <a href="https://github.com/mattbrailsford/umbraco-headrest" rel="noreferrer">Umbraco Headrest project on GitHub</a></p>
  </blockquote>
  <blockquote>
  <p>Note: Each piece of content has a field containing the name of it's content type</p>
  </blockquote>
  <p>For example, the following content structure is valid.</p>
  <pre><code>.
  home
  ├── events
  |   ├── event-one
  |   └── event-two
  |       ├── event-special-offer
  |       └── some-other-content
  ├── special-offer
  └── other-special-offer
  </code></pre>
  <p>So, if we want to render these special offers with nuxt, we will need to use Pages/SpecialOffer.vue.</p>
  <h1>The Problem</h1>
  <p>The problem is that the default paths for these special offers would be:</p>
  <ul>
  <li><code>/special-offer</code></li>
  <li><code>/other-special-offer</code></li>
  <li><code>/events/event-two/event-special-offer</code></li>
  </ul>
  <p>The editors can also create <strong>custom paths</strong>, such as:</p>
  <ul>
  <li><code>/holiday2020/special</code> (which could point to <code>event-special-offer</code>)</li>
  </ul>
  <p>The editors can also <strong>rename content</strong>, so <code>other-special-offer</code> could become <code>new-special-offer</code>. Umbraco will then return <code>new-special-offer</code> for both <code>/other-special-offer</code> and <code>/new-special-offer</code>.</p>
  <h1>Attempted Solutions</h1>
  <p>The following approaches were promising, but either didn't work or were only a partial success.</p>
  <ol>
  <li>Use middleware to forward URL requests to Umbraco and determine the component to load from the result (<strong>fail</strong>)</li>
  <li>Use a plugin to forward URL requests to Umbraco and determine the component to load from the result (<strong>fail</strong>)</li>
  <li>Map the routes from the content (<strong>partial</strong>)</li>
  <li>Use a single page that uses response data to load dynamic components (<strong>partial</strong>)</li>
  </ol>
  <h2>1) The middleware approach</h2>
  <p>We attempted to create an asynchronous call to the CMS's API and then use the response to determine which page to load.</p>
  <p>middleware.js</p>
  <pre class="lang-js prettyprint-override"><code>export default function (context) {
    return new Promise((resolve, reject) =&gt; {
    /** 
     * Prepend the cms provided url's path with /content,
     * which the API knows is for CMS content
     */
      context.app.$axios.$get(`/content${context.route.path}`)
        .then((content) =&gt; {
          // Save the resulting content to vuex
          context.store.commit('umbraco/load', content)
  
          const { routes } = context.app.router.options
  
          /**
           * The content has a contentType property which is a string
           * containing the name of the component we want to load
           * 
           * This finds the corresponding route 
           */
          const wantedRoute = routes.find(x =&gt; x.name === content.contentType)
  
          // Sets the component to load as /Pages/[componentName].vue
          context.route.matched[0].components = {
            default: wantedRoute.component
          }
  
          resolve()
        })
    })
  </code></pre>
  <h3>Issues</h3>
  <p>When running on the server, middleware seems to run <strong>after</strong> the route is resolved, leading to the following error:</p>
  <pre><code>vue.runtime.esm.js?2b0e:619 [Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside &lt;p&gt;, or missing &lt;tbody&gt;. Bailing hydration and performing full client-side render.
  </code></pre>
  <blockquote>
  <p>Note: on first page visit, middleware only runs on the server side and on subsequent visits, middleware only runs on the client side.</p>
  </blockquote>
  <h2>2) The Plugin Approach</h2>
  <p>As plugins run earlier in the lifecycle, we thought we'd try the same approach there and try to call the CMS's API and determine the component to load based off of the result.</p>
  <p>plugin.js</p>
  <pre class="lang-js prettyprint-override"><code>await context.app.router.beforeEach( async ( to, from, next ) =&gt; {
    /** 
     * Prepend the cms provided url's path with /content,
     * which the API knows is for CMS content
     */
    const content = await context.app.$axios.$get( `/content${context.route.path}` );
    
    // Save the resulting content to vuex
    context.store.commit('cms/load', content);
  
    /**
     * The content has a contentType property which is a string
     * containing the name of the component we want to load
     * 
     * This finds the corresponding route 
     */
    const routes = context.app.router.options.routes;
    const targetRoute = routes.find( r =&gt; r.name === content.contentType );
  
    // Sets the component to load as /Pages/[componentName].vue
    to.matched[0].components = {
      default: targetRoute.component
    };
  
    next();
  })
  </code></pre>
  <blockquote>
  <p>Note: Plugins run on both the server and the client on the first page visit. On subsequent visits, they only run on the client.</p>
  </blockquote>
  <h3>Issues</h3>
  <p>Unfortunately this didn't work either as <code>app.router.beforeEach()</code> does not allow for asynchronous code.  As such, the API request never resolved.</p>
  <h2>3) The Route Mapping Approach</h2>
  <p>Generating a list of route maps worked fine unless you changed the routes after the build.  The issue here is that you can't update the routes at runtime, so renaming or adding new content in the CMS would not be mirrored in Nuxt.</p>
  <h2>4) The Dynamic Component Approach</h2>
  <p>This approach seems to be the default solution to our problem, and involves using a single Page in Nuxt that calls the API and then dynamically loads the component.  Unfortunately, this approach removes the majority of Nuxt's page specific features (such as page transitions).</p>
  <p>Losing these features is really unfortunate, as Nuxt is REALLY nice and we'd like to use it as much as possible!</p>
  <h1>Nuxt JS GitHub Issues</h1>
  <ul>
  <li>I raised the failing async issue as <a href="https://github.com/nuxt/nuxt.js/issues/7429" rel="noreferrer">this bug</a> on the nuxt.js GitHub</li>
  <li>I <a href="https://github.com/nuxt/nuxt.js/issues/7437#issuecomment-636694213" rel="noreferrer">commented</a> on the <a href="https://github.com/nuxt/nuxt.js/issues/7437" rel="noreferrer">this GitHub issue</a> which is a feature request for the same problem but has some good dialogue around the issue</li>
  </ul>