Reputation: 1636
I'm refactoring a web app that makes heavy use of vue components.
Currently, the vue components are rendered server side using the twig template language. The components are rendered to the page as static html... no webpack or vue cli is used. Data is dynamic... (product catalog).
You can think of each page as a "single page app" rendered as static html. The templates are a soup of twig and vue.
The long term intention is to move the backend to Phoenix, so I'm trying to make the components as portable as possible, so they're not tightly coupled to twig.
I want to use vue cli, vuex, webpack etc in my refactor. I'd like to render the components server side... however, I am unable to use vue SSR, as we cannot run node on the server.
Is it possible for me to build my components (using webpack etc), upload them to the server - and have them pre-populated server side with data?
One idea I've had (but can't get to work) is to try and place the vuex store outside of my compiled vue app... as a glorified string. I can then write the data to this (using twig) and embed the app within the html that is sent down.
It would still use twig - but only to populate a vuex state object... the rest would be sent down as an html string... and in theory, I'm thinking this shouldn't kill search engines reading the pages?
Upvotes: 3
Views: 2246
Reputation: 3379
The Vue docs mention a few options you could check out.
One particular project which looks promising is prerender-spa-plugin
Upvotes: 2
Reputation: 1636
Ok.... so here is how I have managed to:
Use vue cli and all the cool tooling to build the vue SPA as you normally would
Use https://github.com/SolarLiner/vue-cli-plugin-prerender-spa to easily configure the prerender-spa-plugin through vue cli.
Inside store.js - set your state.... this needs to resemble the shape of your data.
eg:
const state = {
isLoading: false,
products: [
{
name: 'Product 10',
sku: '12345',
price: 300,
stock: 100
}
],
cart: {
}
}
This 'app' has dynamic data. So we need to update the state client side with the correct data. Doing this through an Ajax call inside mounted
would be bonkers, as we will be sending this from the server, and have access to the initial data on the server...
Inside your main.js file, add a mounted hook:
new Vue({
store,
render: h => h(App),
mounted () {
const configElement = document.getElementById('config')
const config = JSON.parse(configElement && configElement.innerHTML)
const initialState = (config && config.initialState)
if (initialState) {
// Set our initial state here - this will overwrite the state
// in our store with the data we want
this.$store.commit('INIT_STATE', initialState)
}
// You'll need this for renderAfterDocumentEvent.
document.dispatchEvent(new Event('render-event'))
}
}).$mount('#app')
and inside your store.mutations - add the 'INIT_STATE' mutation. Eg:
INIT_STATE (state, initialState) {
state.products = initialState
},
You may want to use an action
in your mounted - but as the operation isn't asynchronous, I decided to just go straight to a mutation.
When you build your project - you will end up with an index.html file. Inside index.html, add a JSON script tag above app.js (we do this because we don’t want the browser to think it’s JavaScript).
Use your server-side templating to ‘drop’ in the initial state as a JSON object. Eg
<script id="config" type="application/json">
{{get_my_initial_data_as_JSON()}}
</script>
<script src="/js/app.67487567.js"></script>
Becomes:
<script id="config" type="application/json">
{"initialState":[{"name":"Product 1","sku":"123","price":300,"stock":10},{"name":"Product 2","sku":"234","price":300,"stock":10},{"name":"Product 3","sku":"456","price":300,"stock":10},{"name":"Product 4","sku":"678","price":300,"stock":10}]}
</script>
<script src="/js/app.67487567.js"></script>
If you view index.html's source, you will see that your html contains the initial data that was in your store at Build ( {name: product10}
etc )
However, the store is automatically updated in the mounted hook with the correct data.
I've tested, and google sees and crawls the mounted data.
Benefits:
Updating the initial state this way means NO Ajax call is needed to get the initial data... thus, time to interact and page loads are quicker.
This method removes the server-side soup of hardwiring vue templates into server-side templates. You don’t have to duplicate your template in two templating languages (eg vue and twig, or a mix of twig to output vue). It untangles your vue app from the backend.
The biggest benefit is being able to use Vue Cli’s tooling and Webpack - which was almost impossible to use before due to this server-side soup.
** EDIT **
Thinking about it - you can just go into /public/index.html and insert JSON script tag above the lines
If you add the server side method that you intend to use to write the JSON here - eg:
<script id="config" type="application/json">
{{get_my_initial_data_as_JSON()}}
</script>
You don't have any client side files to edit :) you just gotta get that file served from your server - and have your get_my_initial_data_as_JSON()
ready to populate it.
Upvotes: 2