# Layouts and advanced output operations
Starting from version 1.4.0, Vue Storefront allows you to switch the HTML templates and layouts dynamically in the SSR mode.
This feature can be very useful for non-standard rendering scenarios like:
- Generating the XML output
- Generating the AMPHTML pages
- Generating widgets without
<head>section
# How it works
Before 1.4.0, Vue Storefront generated the output by a mix of:
- Taking the base HTML template
src/index.template.html, - Rendering the
src/themes/default/App.vueroot component, - Injecting the Vue SSR output into the template + adding CSS styles, script references etc. Read more on Vue SSR Styles and Scripts injection (opens new window)
This mode is still in place and is enabled by default. What we've changed is that you can now select which HTML template and layout your app is routing in per-route manner.
# Changelog
The changes we’ve introduced include:
- Distinct routes can now set
context.output.templateinasyncDatamethod. By doing so, you can skip usingdist/index.html(which contains typical HTML5 elements - like<head>). This is important when we're going to generate either AMPHTML pages (that cannot contain any<script>tags) or XML files - you name it. - Distinct routes can set
meta.layoutand by doing so, switch the previously constantApp.vuelayout file. - Access to server
contextobject inasyncDataand two new features -output.prependandoutput.appendhave been created to allow you to control the rendering flow of the template.
# Templates
We added two new HTML templates + two Vue layouts.
Templates:
index.basic.template.html- basic elementsindex.minimal.template.html- it contains the standard HTML markup without any additional injects, so when you render a Vue component, its output will be pasted into<body>and that's all. Probably good starting point for AMPHTML implementation (opens new window)
You can add more templates. All you need is to set the proper config.ssr.templates variable in config/local.json:
"ssr": {
"templates": {
"default": "dist/index.html",
"minimal": "dist/index.minimal.html",
"basic": "dist/index.basic.html"
},
"executeMixedinAsyncData": true,
"initialStateFilter": ["config", "__DEMO_MODE__", "version", "storeView"],
"useInitialStateFilter": true
},
Templates paths are relative to the root folder of vue-storefront. You can also set the template to none ("") to skip it. This option can be useful for XML / JSON / widgets rendering that does not require the whole HTML layout.
# Examples
You can find some examples in the src/extensions/raw-output-example example.
# Generating the XML output
Example URL: http://localhost:3000/raw-output-example.xml
Route setup to switch the Vue layout:
{ path: '/raw-output-example.xml', component: RawOutputExample, meta: { layout: 'empty' } }
Vue component to render the XML:
<template>
<raw-content>
This page is using empty layout set in routes + no HTML template
</raw-content>
</template>
<script>
export default {
name: 'RawOutputExample',
asyncData ({ store, route, context }) {
contextserver.response.setHeader('Content-Type', 'text/xml')
context.output.template = ''
return new Promise((resolve, reject) => {
resolve()
})
},
data () {
return {
'exampleData': 'Data from base component'
}
},
components: {
}
}
</script>
The key part is:
contextserver.response.setHeader('Content-Type', 'text/xml');
context.output.template = '';
These two statements:
- Set the HTTP header (by accessing ExpressJS response object—
contextserver.response. There is alsocontextserver.requestandcontext.app- the ExpressJS application)- setoutput.templateto none, which will skip the HTML template rendering at all.
# Switching off layout and injecting dynamic content
Example URL: http://localhost:3000/append-prepend.html
Route setup to switch the Vue layout:
{ path: '/append-prepend.html', component: NoLayoutAppendPrependExample, meta: { layout: 'empty' } },
Vue component to render the XML:
<template>
<div>This page is rendered with no JavaScripts attached - no layout at all</div>
</template>
<script>
export default {
name: 'NoJSExample',
asyncData ({ store, route, context }) {
context.output.template = ''
context.output.append = (context) => {
return '<div>This content has been dynamically appended</div>'
}
context.output.prepend = (context) => {
return '<div>this content has been dynamically prepended</div>'
}
return new Promise((resolve, reject) => {
resolve()
})
},
data () {
return {
'exampleData': 'Data from base component'
}
},
components: {
}
}
</script>
The key part is:
context.output.template = '';
context.output.append = context => {
return '<div>This content has been dynamically appended</div>';
};
context.output.prepend = context => {
return '<div>this content has been dynamically prepended</div>';
};
These two statements:
- Set
output.templateto none, which will cause to skip the HTML template rendering at all. - Add the
output.appendandoutput.prependmethods to the server context.
The output will be generated with this logic:
const contentPrepend =
typeof context.output.prepend === 'function'
? context.output.prepend(context)
: '';
const contentAppend =
typeof context.output.append === 'function'
? context.output.append(context)
: '';
output = contentPrepend + output + contentAppend;
Please note that the context contains a lot of interesting features you can use to control the CSS, SCRIPT and META injection. Read more on Vue SSR Styles and Scripts injection (opens new window)
Note: The context object = Vue.prototype.$ssrContext (opens new window)
# Output compression
HTML Minifier has been added to Vue Storefront 1.11. To enable this feature please switch the config.server.useHtmlMinifier. You can set the specific configuration of the htmlMinifier using the config.server.htmlMinifierOptions. Read more on the available configuration (opens new window). The minified output is then being cached by SSR Output cache mechanism.
Output compression has been also enabled (if the src/modules/server.ts contains the compression module on the list). By default it works just for production builds. It uses the gzip compression by default. Read more about the compression module (opens new window) that we're using for this implementation.