vuejs / vue-hackernews-2.0

HackerNews clone built with Vue 2.0, vue-router & vuex, with server-side rendering
MIT License
10.96k stars 2.15k forks source link

Server side state is not available before router can make decision #346

Closed vovopap closed 5 years ago

vovopap commented 5 years ago

I need the store to be available in my createRouter function app.js:

export function createApp () {
  // create store and router instances
  const store = createStore()
  const router = createRouter(store)
  ...
}

so, I can use router.beforeEach hook to decide if authentication is required router/index.js:

export function createRouter (store) {
  let router = new Router({
     ...
  })
  router.beforeEach((to, from, next) => {
    let requiresAuth = to.matched.some(record => record.meta.requiresAuth);
    if(requiresAuth && !store.state.username) next({name: 'login'}) // username is NOT AVAILABLE
    else next()
  })
  return router;
}

The problem is that store.state.username is not available during the initial route resolve. That is because the state is replaced after router resolves the initial route resolve entry-client.js:

const { app, router, store } = createApp()

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

How can I make it so state from SSR is available before routing kicks off? Could you guide me to the right direction?

darkiron commented 5 years ago

Hi, If you read entry-server.js

store is linked to the context. beforeEach maybe not available in server side, but you cant check authenticate in onReady in head

In seconde time, when you load your app the store was empty you need to load all informations before routing (via DB, or cookies/header)

If you doesn't load data you can check the store with vue devtools.

window object is not available in server-side.

vovopap commented 5 years ago

@darkiron I understand all points you made.

window object is not avaliable, becuase we are writing universal code. And that is the reason I opted for store.state to store authentication data.

In server side, we push the url to router directly in the code: entry-server.js:

// fill state with necessary data here
// set router's location
 router.push(url)

And I am able to fill my state with logged-in user info (that I use in my guard logic), before pushing url

The problem is with client dehydration. I cannot effectively have access to state before router resolve starts.

darkiron commented 5 years ago

If you write universal code, the store was hydrating with same data.( it's was reload with client-side)

push your code on a repo. Can be better way to get help.

vovopap commented 5 years ago

Okay, finally came up with the following solution:

Extract the logic of creating store from createApp to entry-script.js and entry-server.js. Pass store to createApp as an argument:

// app.js
export function createApp (store) {
  // create router instances
  const router = createRouter(store)
 ...
}

and in entry-server.js:

const store = createStore()
const { app, router } = createApp(store)

and in entry-client.js:

const store = createStore()
// prime the store with server-initialized state.
// the state is determined during SSR and inlined in the page markup.
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}
const { app, router } = createApp(store)

This way, store is replaced with the state from ssr before creating the app.