Nuxt.js: Render XML pages Server Side using Vue templates

Created on 12 Jun 2019  路  7Comments  路  Source: nuxt/nuxt.js

What problem does this feature solve?

Would be nice to render XML sitemaps, RSS feeds, Google Shopping feeds and similar stuff using Nuxt.js.

Why do I think this a good feature?

Because at runtime we have access to Nuxt plugins such as translation, routing and API clients (data and functionality). These plugins are often tightly coupled.

When writing stuff like this in a Nuxt module we often need to replicate functionality which is already available in the Nuxt context. Additionally, it's really easy and logically to render XML using Vue templates and more in line with the rest of the application.

What does the proposed changes look like?

Easiest solution in my eyes are adding a flag property to a page component saying it should be serverSideOnly. If this flag is set to true, it should be able to tell Nuxt that there shouldn't be rendered any layout: layout: null.

I think there should be some minor additional changes to the webpack integration for the HTML template. Which obviously, shouldn't be included in the response.

Last but not least: serverSideOnly page components should not be in the client bundle. (Including the routes).

Simple example

File: ~/pages/xml-feeds/sitemap.xml.vue:

<template>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
        <url>
            <loc>{{generateURL('index')}}</loc>
            <changefreq>weekly</changefreq>
            <priority>1.0</priority>
        </url>
    </urlset>
</template>
<script>

export default {
    layout: null,
    serverSideOnly: true,

    methods: {
        generateURL(routeName) {
            return 'https://example.com' + this.$router.resolve(routeName).href;
        }
    }
}
</script>

This feature request is available on Nuxt community (#c9347)
discussion feature-request

Most helpful comment

Yes indeed, I agree.

However, template is already a component property (specifying the template literal). Maybe also a bit ambigious.

What would be nice is also to be able to specify how the output is wrapped. Like:

export default {
  serverSideOnly: true,
  template: null,

  wrapper: null
}

Specifying that the output is not wrapped at all.

Wrapper could be defaulted to the Nuxt wrapper, and overridden using a custom function:

import mjml2html from 'mjml'

export default {
  serverSideOnly: true,
  template: null,

  wrapper: (outputString) => {
     return mjml2html(outputString);
  }
}

https://github.com/mjmlio/mjml

I can think of endless possibilities this way!

All 7 comments

I agree, this would be very useful to have! +1

I came here looking for the answer to a similar question: (Feel free to remove this comment in case it does not match the intention of the original issue)

Is there a way to render a page without a template? My use case would be to provide HTML fragments which could be included elsewhere (i.e. as part of a micro-frontend style application). In Nuxt-terms I need a page to be rendered without "the frame", consisting of layout and template (basically: skip rendering them as a wrapper).

@dennisreimann
Seems like a good use case for this feature.

Also I had something related in mind:
I want to render mjml templates in Vue using Nuxt.js.

This way I can leverage Nuxt.js plugins and Vue to render the template and feed that to mjml. Also easy to do using this feature!

Looks cool, the micro-frontend concept by the way!

From a users perspective I can imagine this as the option to specify the template for a page, just like the layout. It'd be nice if both options could also be explicitely set to null, skipping the wrapping of the page.

In my case I'd rather go with an additional template, because I need the ability to include the {{ HEAD }} part (as it contains the assets needed in the fragment. Rright now I found only the "hook" to change the app.html file for every page, but not the option to specify an individual template like fragment.html for some pages.

Yes indeed, I agree.

However, template is already a component property (specifying the template literal). Maybe also a bit ambigious.

What would be nice is also to be able to specify how the output is wrapped. Like:

export default {
  serverSideOnly: true,
  template: null,

  wrapper: null
}

Specifying that the output is not wrapped at all.

Wrapper could be defaulted to the Nuxt wrapper, and overridden using a custom function:

import mjml2html from 'mjml'

export default {
  serverSideOnly: true,
  template: null,

  wrapper: (outputString) => {
     return mjml2html(outputString);
  }
}

https://github.com/mjmlio/mjml

I can think of endless possibilities this way!

was also looking for this and came up with this workaround, a little hacky but does the job
on the nuxt config files, add a new hook

hooks: {
      'render:route': (url, page, { req, res }) => {
        if (url.match('/api/')){
          let match = page.html.match(/<div id="__layout">(.+?)<\/div>/)
          if (match && match.length) page.html = `<?xml version="1.0" encoding="UTF-8"?>${match[1]}`
        }
     }
}

on the .vue page file I have something like this

<template lang="pug">
mrss(xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:nonamespaceschemalocation='ingestion.xsd')
  channel
    item
      title {{ catalogue_entry.title_en }}
      slug {{ catalogue_entry.slug_en }}

which outputs this

<?xml version="1.0" encoding="UTF-8"?>
<mrss xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nonamespaceschemalocation="ingestion.xsd">
   <channel>
      <item>
         <title>When I Dance</title>
         <slug>when-i-dance</slug>

Now I can write XML with PUG and VUE, so happy!! :D

@dseeker great solution!

However this doesn't set the proper http headers (mainly the Content-Type). I tried manually setting it using res.setHeader, but it seems like nuxt overrides this somewhere before sending it.

I couldn't find any appropriate hooks to defer setting the http header to the last minute to avoid nuxt from overriding the header.

Do you have any solutions for this? Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mikekidder picture mikekidder  路  3Comments

nassimbenkirane picture nassimbenkirane  路  3Comments

bimohxh picture bimohxh  路  3Comments

vadimsg picture vadimsg  路  3Comments

gary149 picture gary149  路  3Comments