arrow_back What's the SEO situation with multi-page applications in Vue?

1 vote
Hello, everyone.

I'm currently working on an online store website.
The back is Wordpress with Woocommerce, on the front I plan to use Vue.
The customer started talking about SEO. And then it became sad. Because, as far as I have heard, with SEO sites that use technology client rendering markup, things are not very good.

In short - how will a search robot evaluate a page if it is rendered entirely on the client?

(the server sends only html with one element in the body).
But at the same time asynchronously no data is requested, all that is needed is already at the front (either "written" directly into the front, or transferred to the front along with the markup).

For example as in this site .
According to Wappalyzer, the site uses Vue.js, and if you look at the markup, it consists of components, not the usual html tags. How will such a site be indexed? After all, you can't see its metrics.

Thank you all in advance for your answers.

2 Answers

0 votes
As an example of prendering, bring here what needs to be indexed
// vue.config.js
var path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
configureWebpack: {
module: {
rules: [
test: /\assets\/.*.css$/i,
use: ['style-loader', 'css-loader'],
plugins: [
new BundleAnalyzerPlugin(),
new PrerenderSPAPlugin({
// Required - The path to the webpack-outputted app to prerender.
staticDir: path.join(__dirname, 'dist'),

// Optional - The path your rendered app should be output to.
// (Defaults to staticDir.)
outputDir: path.join(__dirname, 'prerendered'),

// Optional - The location of index.html
indexPath: path.join(__dirname, 'dist', 'index.html'),

// Required - Routes to render.
routes: ['/', '/about'],

// Optional - Allows you to customize the HTML and output path before
// writing the rendered contents to a file.
// renderedRoute can be modified and it or an equivelant should be returned.
// renderedRoute format:
// {
// route: String, // Where the output file will end up (relative to outputDir)
// originalRoute: String, // The route that was passed into the renderer, before redirects.
// html: String, // The rendered HTML for this route.
// outputPath: String // The path the rendered HTML will be written to.
// }
postProcess(renderedRoute) {
// Ignore any redirects.
renderedRoute.route = renderedRoute.originalRoute;
// Basic whitespace removal. (Don't use this in production.)
renderedRoute.html = renderedRoute.html.split(/>[\s]+</gmi).join('><')
// Remove /index.html from the output path if the dir name ends with a .html file extension.
// For example: /dist/dir/special.html/index.html -> /dist/dir/special.html
if (renderedRoute.route.endsWith('.html')) {
renderedRoute.outputPath = path.join(__dirname, 'dist', renderedRoute.route);

return renderedRoute;

// Optional - Uses html-minifier (
// To minify the resulting HTML.
// Option reference:
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true

// Server configuration options.
server: {
// Normally a free port is autodetected, but feel free to set this if needed.
port: 8001

// The actual renderer to use. (Feel free to write your own)
// Available renderers:
renderer: new Renderer({
// Optional - The name of the property to add to the window object with the contents of `inject`.
// injectProperty: '__PRERENDER_INJECTED',
// Optional - Any values you'd like your app to have access to via `window.injectProperty`.
// inject: {
// foo: 'bar'
// },

// Optional - defaults to 0, no limit.
// Routes are rendered asynchronously.
// Use this to limit the number of routes rendered in parallel.
maxConcurrentRoutes: 4,

// Optional - Wait to render until the specified event is dispatched on the document.
// eg, with `document.dispatchEvent(new Event('custom-render-trigger'))`
// renderAfterDocumentEvent: 'custom-render-trigger',

// Optional - Wait to render until the specified element is detected using `document.querySelector`
// renderAfterElementExists: 'my-app-element',

// Optional - Wait to render until a certain amount of time has passed.
// renderAfterTime: 5000, // Wait 5 seconds.

// Other puppeteer options.
// (See here:
headless: true // было false Display the browser window when rendering. Useful for debugging.


If only it were that simple.
Requires adequate indexing of all pages, not just certain pages.
But in any case, I will keep your suggestion in mind, thank you.
Kirill Kosarev There's a whole bunch of options out there. My option is for a personal page :)

For the store as an option to render on the server if the bot came to you and give statics, the other option, which was used is a phantom to go through all the dynamics and save the page.
Kirill Kosarev I do not remember exactly, but I must google it. There is a technology - for search bots to give ssr - there is no need for authorization, you can ignore the styles and generally facilitate the page as much as possible, including in terms of speed of return. And when you come in a real user is already full throttle to use all the advantages View.
2 votes
SSR and everything will be just right


Yes, SSR is ideal. But in the conditions of the customer's hosting I have only PHP. And for SSR you still need Node.js environment.
Kirill Kosarev , and then why WP?
Darkhan Kamaliev I would really hate to have to do this.
Better to make the customer change the host and make PHP and Node.js work hand in hand
Kirill Kosarev , you can shoot yourself in the knee with phpv8js