raukaute / vue-hackernews-3.0

HackerNews clone built with Vue 3.0, vue-router@next & vuex@next, with server-side rendering
73 stars 13 forks source link

Source code print always the code of root render #5

Open WilliamFalci opened 3 years ago

WilliamFalci commented 3 years ago

Hello,

I implemented my project with your ssr configuration, everything work good, Im able to run the project ect... but I have a weird issue, I tried everything but I not find solution.

The problem is that, the source code of page is always the source code of the the root page, always, any idea of what can be and how fix it? I will be happy to give more information etc:

Package.json

{
  "name": "euroingro-ssr",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "start:dev": "cross-env NODE_ENV=development node server",
    "start:prod": "cross-env NODE_ENV=production node server",
    "build": "rimraf dist && npm run build:client && npm run build:server",
    "build:client": "webpack --env prod --config build/webpack.client.config.js --progress --profile --json > stats.client.json",
    "build:server": "webpack --env prod --config build/webpack.server.config.js --progress --profile --json > stats.server.json",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "@fortawesome/fontawesome-free": "^5.15.2",
    "@vueform/multiselect": "^1.5.0",
    "@vueuse/head": "^0.6.0",
    "axios": "^0.21.1",
    "bootstrap": "^5.0.2",
    "core-js": "^3.6.5",
    "cross-env": "^7.0.2",
    "custom-env": "^2.0.1",
    "dotenv-webpack": "^7.0.3",
    "file-loader": "^6.2.0",
    "locale": "^0.1.0",
    "primeicons": "^4.1.0",
    "primevue": "^3.5.0",
    "serialize-javascript": "^5.0.1",
    "serve-favicon": "^2.5.0",
    "vue": "^3.1.4",
    "vue-gtag-next": "^1.14.0",
    "vue-i18n": "^9.1.0",
    "vue-lottie": "^0.2.1",
    "vue-router": "^4.0.8",
    "vuex": "^4.0.2",
    "vuex-router-sync": "^5.0.0",
    "xml-loader": "^1.2.1"
  },
  "devDependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "@vue/compiler-sfc": "^3.0.0",
    "@vue/server-renderer": "^3.1.4",
    "babel-eslint": "^10.1.0",
    "babel-loader": "^8.1.0",
    "babel-plugin-transform-remove-console": "^6.9.4",
    "css-loader": "^4.3.0",
    "css-minimizer-webpack-plugin": "^1.1.5",
    "eslint": "^7.10.0",
    "eslint-plugin-vue": "^7.0.1",
    "eslint-webpack-plugin": "^2.1.0",
    "express": "^4.17.1",
    "hash-sum": "^2.0.0",
    "lodash.uniq": "^4.5.0",
    "mini-css-extract-plugin": "^0.11.2",
    "postcss": "^8.1.1",
    "postcss-loader": "^4.0.4",
    "rimraf": "^3.0.2",
    "sass": "^1.27.0",
    "sass-loader": "^10.0.3",
    "terser-webpack-plugin": "^4.2.3",
    "url": "^0.11.0",
    "url-loader": "^4.1.1",
    "vue-bundle-renderer": "0.0.3",
    "vue-loader": "^15.9.3",
    "vue-loader-v16": "npm:vue-loader@^16.0.0-beta.7",
    "webpack": "^5.1.3",
    "webpack-cli": "^4.1.0",
    "webpack-dev-middleware": "^3.7.2",
    "webpack-hot-middleware": "^2.25.0",
    "webpack-merge": "^5.1.4",
    "webpack-node-externals": "^3.0.0",
    "workbox-webpack-plugin": "^5.1.4"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/vue3-essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

server.js

if(process.env.NODE_ENV == undefined){
  require('custom-env').env('development')
}else{
  require('custom-env').env(process.env.NODE_ENV)
}

const fs = require('fs'),
  path = require('path'),
  resolve = (file) => path.resolve(__dirname, file);

const express = require('express'),
  favicon = require('serve-favicon'),
  app = express();

const serialize = require('serialize-javascript');
const { createBundleRenderer } = require('vue-bundle-renderer');

const isProd = process.env.NODE_ENV === 'production';

console.log(process.env)

function createRenderer(bundle, options) {
  return createBundleRenderer(
    bundle,
    Object.assign(options, {
      runInNewContext: false,
      vueServerRenderer: require('@vue/server-renderer'),
      basedir: resolve('./dist'),
      publicPath: '/dist/',
    })
  );
}

let renderer, readyPromise;

if (isProd) {
  const bundle = require('./dist/vue-ssr-server-bundle.json'),
    clientManifest = require('./dist/vue-ssr-client-manifest.json');

  renderer = createRenderer(bundle, { clientManifest });
} else {
  readyPromise = require('./build/setup-dev-server')(app, (bundle, options) => {
    renderer = createRenderer(bundle, options);
  });
}

const serve = (path, cache) =>
  express.static(resolve(path), {
    maxAge: cache && isProd ? 1000 * 60 * 60 * 24 * 30 : 0,
  });

app.use(favicon('./public/favicon.ico'));
app.use('/dist', serve('./dist', true));

async function render(req, res) {
  const handleError = (err) => {
    res.status(500).send('500 | Internal Server Error');
    console.error(`error during render : ${req.url}`);
    console.error(err);
  };

  const renderState = (context) => {
    const contextKey = 'state';
    const windowKey = '__INITIAL_STATE__';
    const state = serialize(context[contextKey]);
    const autoRemove =
      ';(function(){var s;(s=document.currentScript||document.scripts[document.scripts.length-1]).parentNode.removeChild(s);}());';
    var nonceAttr = context.nonce ? ' nonce="' + context.nonce + '"' : '';
    return context[contextKey]
      ? '<script' +
          nonceAttr +
          '>window.' +
          windowKey +
          '=' +
          state +
          autoRemove +
          '</script>'
      : '';
  };

  const context = {
    url: req.url,
  };

  let page;
  try {
    page = await renderer.renderToString(context);
  } catch (err) {
    handleError(err);
  }
  let { renderStyles, renderResourceHints, renderScripts } = context;

  // TODO: Use loadash template
  const html = `
        <!DOCTYPE html>
            <html lang="en">
              <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              ${renderResourceHints()}
              ${renderStyles()}
              <title>SSR Vue 3</title>
              </head>
              <body>
                <div id="app">${page}</div>
                ${renderScripts()}
                ${renderState(context)}
                </body>
            </html>
        `;

  // print page to file for inspection
  if (!isProd) {
    fs.writeFile('rendered.html', html, (err) => {
      if (err) {
        throw err;
      }
    });
  }
  res.setHeader('Content-Type', 'text/html');
  res.send(html);
}

app.get(
  '*',
  isProd
    ? render
    : async (req, res) => {
        await readyPromise;

        render(req, res);
      }
);

const port = process.env.PORT || 8080;
app.listen(port, () => {
  console.log(`Server started at http://localhost:${port}`);
});

app.js

import { createSSRApp } from 'vue';
import { _createRouter } from './router';
import { _createStore } from './store';
import { sync } from 'vuex-router-sync';
import App from './App.vue';
import i18n from '@/plugins/i18n.js'

import 'bootstrap/dist/css/bootstrap.min.css'
import '@fortawesome/fontawesome-free/js/all.js'
import "@vueform/multiselect/themes/default.css"
import './static/css/saga-euroingro.css'
import './static/css/flag-icon.css'

import './static/css/quantityPicker.css'
import VueGtag from "vue-gtag-next";

import PrimeVue from 'primevue/config'
import Dialog from 'primevue/dialog'
import ToastService from 'primevue/toastservice';

export function _createApp() {
  const app = createSSRApp(App),
    router = _createRouter(),
    store = _createStore();

  sync(store, router);

  app
    .use(router)
    .use(store)
    .use(i18n)
    .use(PrimeVue)
    .use(ToastService)
    .use(VueGtag, {
      property: {
        id: process.env.VUE_APP_GAL
      }
    })
    .component('Dialog', Dialog)

    app.config.compilerOptions.isCustomElement = tag => tag.startsWith('lord-')

  return { app, router, store };
}

client-entry.js

import { _createApp } from './app';
import 'bootstrap/dist/js/bootstrap.bundle.min.js'

const { app, router, store } = _createApp();

if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__);
}

(async (r, a) => {
  await r.isReady();
  a.mount('#app', true);
})(router, app);

server-entry.js

import { _createApp } from './app';

export default async ssrContext => {
    const { app, router, store } = _createApp();
    const { url } = ssrContext;

    router.push(url);

    await router.isReady();

    ssrContext.state = store.state
    return app;
}

store/index.js

import { createStore } from 'vuex';

// Load all modules.
function loadModules() {
  const context = require.context("@/store/modules/", false, /([a-z_]+)\.js$/i)

  const modules = context
    .keys()
    .map((key) => ({ key, name: key.match(/([a-z_]+)\.js$/i)[1] }))
    .reduce(
      (modules, { key, name }) => ({
        ...modules,
        [name]: context(key).default
      }),
      {}
    )

  console.log('############################## MODULES')
  console.log(modules)

  return { context, modules }
}

const { context, modules } = loadModules()

if (module.hot) {
  // Hot reload whenever any module changes.
  module.hot.accept(context.id, () => {
    const { modules } = loadModules()

    store.hotUpdate({
      modules
    })
  })
}

export function _createStore() {
  return createStore({
    modules
  })
}

router/index.js

import { createStore } from 'vuex';

// Load all modules.
function loadModules() {
  const context = require.context("@/store/modules/", false, /([a-z_]+)\.js$/i)

  const modules = context
    .keys()
    .map((key) => ({ key, name: key.match(/([a-z_]+)\.js$/i)[1] }))
    .reduce(
      (modules, { key, name }) => ({
        ...modules,
        [name]: context(key).default
      }),
      {}
    )

  console.log('############################## MODULES')
  console.log(modules)

  return { context, modules }
}

const { context, modules } = loadModules()

if (module.hot) {
  // Hot reload whenever any module changes.
  module.hot.accept(context.id, () => {
    const { modules } = loadModules()

    store.hotUpdate({
      modules
    })
  })
}

export function _createStore() {
  return createStore({
    modules
  })
}

router/routes.js

import productUrls from '@/views/product/urls.js'
import aboutUsUrls from '@/views/about_us/urls.js'

import sellWithUsUrls from '@/views/sell_with_us/urls.js'
import shipmentSafetyUrls from '@/views/shipment_safety/urls.js'

var childrens = [{
  path: '',
  name: 'home',
  props: true,
  component: () => import('@/views/home/view.vue')
}]

//CONCAT COMPONENTS URLS
childrens = [].concat.apply(childrens, productUrls)
childrens = [].concat.apply(childrens, aboutUsUrls)
childrens = [].concat.apply(childrens, sellWithUsUrls)
childrens = [].concat.apply(childrens, shipmentSafetyUrls)

export const routes = [{
  path: '/:lang',
  props: true,
  component: () => import('@/views/appWrapper.vue'),
  children: childrens
}]
WilliamFalci commented 3 years ago

Update:

Problem solved, I forgot to push the router from server-side