server.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import Vue from 'vue'
  2. import { joinURL, normalizeURL, withQuery } from 'ufo'
  3. <% if (fetch.server) { %>import fetch from 'node-fetch'<% } %>
  4. <% if (features.middleware) { %>import middleware from './middleware.js'<% } %>
  5. import {
  6. <% if (features.asyncData) { %>applyAsyncData,<% } %>
  7. <% if (features.middleware) { %>middlewareSeries,<% } %>
  8. <% if (features.middleware && features.layouts) { %>sanitizeComponent,<% } %>
  9. getMatchedComponents,
  10. promisify
  11. } from './utils.js'
  12. <% if (features.fetch) { %>import fetchMixin from './mixins/fetch.server'<% } %>
  13. import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
  14. import NuxtLink from './components/nuxt-link.server.js' // should be included after ./index.js
  15. <% if (features.fetch) { %>
  16. // Update serverPrefetch strategy
  17. Vue.config.optionMergeStrategies.serverPrefetch = Vue.config.optionMergeStrategies.created
  18. // Fetch mixin
  19. if (!Vue.__nuxt__fetch__mixin__) {
  20. Vue.mixin(fetchMixin)
  21. Vue.__nuxt__fetch__mixin__ = true
  22. }
  23. <% } %>
  24. <% if (isDev) { %>
  25. if (!Vue.__original_use__) {
  26. Vue.__original_use__ = Vue.use
  27. Vue.__install_times__ = 0
  28. Vue.use = function (plugin, ...args) {
  29. plugin.__nuxt_external_installed__ = Vue._installedPlugins.includes(plugin)
  30. return Vue.__original_use__(plugin, ...args)
  31. }
  32. }
  33. if (Vue.__install_times__ === 2) {
  34. Vue.__install_times__ = 0
  35. Vue._installedPlugins = Vue._installedPlugins.filter(plugin => {
  36. return plugin.__nuxt_external_installed__ === true
  37. })
  38. }
  39. Vue.__install_times__++
  40. <% } %>
  41. // Component: <NuxtLink>
  42. Vue.component(NuxtLink.name, NuxtLink)
  43. <% if (features.componentAliases) { %>Vue.component('NLink', NuxtLink)<% } %>
  44. <% if (fetch.server) { %>if (!global.fetch) { global.fetch = fetch }<% } %>
  45. const noopApp = () => new Vue({ render: h => h('div', { domProps: { id: '<%= globals.id %>' } }) })
  46. const createNext = ssrContext => (opts) => {
  47. // If static target, render on client-side
  48. ssrContext.redirected = opts
  49. if (ssrContext.target === 'static' || !ssrContext.res) {
  50. ssrContext.nuxt.serverRendered = false
  51. return
  52. }
  53. let fullPath = withQuery(opts.path, opts.query)
  54. const $config = ssrContext.runtimeConfig || {}
  55. const routerBase = ($config._app && $config._app.basePath) || '<%= router.base %>'
  56. if (!fullPath.startsWith('http') && (routerBase !== '/' && !fullPath.startsWith(routerBase))) {
  57. fullPath = joinURL(routerBase, fullPath)
  58. }
  59. // Avoid loop redirect
  60. if (decodeURI(fullPath) === decodeURI(ssrContext.url)) {
  61. ssrContext.redirected = false
  62. return
  63. }
  64. ssrContext.res.writeHead(opts.status, {
  65. Location: normalizeURL(fullPath)
  66. })
  67. ssrContext.res.end()
  68. }
  69. // This exported function will be called by `bundleRenderer`.
  70. // This is where we perform data-prefetching to determine the
  71. // state of our application before actually rendering it.
  72. // Since data fetching is async, this function is expected to
  73. // return a Promise that resolves to the app instance.
  74. export default async (ssrContext) => {
  75. // Create ssrContext.next for simulate next() of beforeEach() when wanted to redirect
  76. ssrContext.redirected = false
  77. ssrContext.next = createNext(ssrContext)
  78. // Used for beforeNuxtRender({ Components, nuxtState })
  79. ssrContext.beforeRenderFns = []
  80. // Nuxt object (window.{{globals.context}}, defaults to window.__NUXT__)
  81. ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], <% if (features.fetch) { %>fetch: {}, <% } %>error: null<%= (store ? ', state: null' : '') %>, serverRendered: true, routePath: '' }
  82. <% if (features.fetch) { %>
  83. ssrContext.fetchCounters = {}
  84. <% } %>
  85. // Remove query from url is static target
  86. <% if (isFullStatic) { %>
  87. if (ssrContext.url) {
  88. ssrContext.url = ssrContext.url.split('?')[0]
  89. }
  90. <% } %>
  91. // Public runtime config
  92. ssrContext.nuxt.config = ssrContext.runtimeConfig.public
  93. if (ssrContext.nuxt.config._app) {
  94. __webpack_public_path__ = joinURL(ssrContext.nuxt.config._app.cdnURL, ssrContext.nuxt.config._app.assetsPath)
  95. }
  96. // Create the app definition and the instance (created for each request)
  97. const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext, ssrContext.runtimeConfig.private)
  98. const _app = new Vue(app)
  99. // Add ssr route path to nuxt context so we can account for page navigation between ssr and csr
  100. ssrContext.nuxt.routePath = app.context.route.path
  101. <% if (features.meta) { %>
  102. // Add meta infos (used in renderer.js)
  103. ssrContext.meta = _app.$meta()
  104. <% } %>
  105. <% if (features.asyncData) { %>
  106. // Keep asyncData for each matched component in ssrContext (used in app/utils.js via this.$ssrContext)
  107. ssrContext.asyncData = {}
  108. <% } %>
  109. const beforeRender = async () => {
  110. // Call beforeNuxtRender() methods
  111. await Promise.all(ssrContext.beforeRenderFns.map(fn => promisify(fn, { Components, nuxtState: ssrContext.nuxt })))
  112. <% if (store) { %>
  113. ssrContext.rendered = () => {
  114. // Add the state from the vuex store
  115. ssrContext.nuxt.state = store.state
  116. <% if (isFullStatic && store) { %>
  117. // Stop recording store mutations
  118. ssrContext.unsetMutationObserver()
  119. <% } %>
  120. }
  121. <% } %>
  122. }
  123. const renderErrorPage = async () => {
  124. // Don't server-render the page in static target
  125. if (ssrContext.target === 'static') {
  126. ssrContext.nuxt.serverRendered = false
  127. }
  128. <% if (features.layouts) { %>
  129. // Load layout for error page
  130. const layout = (NuxtError.options || NuxtError).layout
  131. const errLayout = typeof layout === 'function' ? layout.call(NuxtError, app.context) : layout
  132. ssrContext.nuxt.layout = errLayout || 'default'
  133. await _app.loadLayout(errLayout)
  134. _app.setLayout(errLayout)
  135. <% } %>
  136. await beforeRender()
  137. return _app
  138. }
  139. const render404Page = () => {
  140. app.context.error({ statusCode: 404, path: ssrContext.url, message: '<%= messages.error_404 %>' })
  141. return renderErrorPage()
  142. }
  143. <% if (debug) { %>const s = Date.now()<% } %>
  144. // Components are already resolved by setContext -> getRouteData (app/utils.js)
  145. const Components = getMatchedComponents(app.context.route)
  146. <% if (store) { %>
  147. /*
  148. ** Dispatch store nuxtServerInit
  149. */
  150. if (store._actions && store._actions.nuxtServerInit) {
  151. try {
  152. await store.dispatch('nuxtServerInit', app.context)
  153. } catch (err) {
  154. console.debug('Error occurred when calling nuxtServerInit: ', err.message)<%= isTest ? '// eslint-disable-line no-console' : '' %>
  155. throw err
  156. }
  157. }
  158. // ...If there is a redirect or an error, stop the process
  159. if (ssrContext.redirected) {
  160. return noopApp()
  161. }
  162. if (ssrContext.nuxt.error) {
  163. return renderErrorPage()
  164. }
  165. <% } %>
  166. <% if (features.middleware) { %>
  167. /*
  168. ** Call global middleware (nuxt.config.js)
  169. */
  170. let midd = <%= serialize(router.middleware).replace('middleware(', 'function(') %><%= isTest ? '// eslint-disable-line' : '' %>
  171. midd = midd.map((name) => {
  172. if (typeof name === 'function') {
  173. return name
  174. }
  175. if (typeof middleware[name] !== 'function') {
  176. app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
  177. }
  178. return middleware[name]
  179. })
  180. await middlewareSeries(midd, app.context)
  181. // ...If there is a redirect or an error, stop the process
  182. if (ssrContext.redirected) {
  183. return noopApp()
  184. }
  185. if (ssrContext.nuxt.error) {
  186. return renderErrorPage()
  187. }
  188. <% } %>
  189. <% if (isFullStatic && store) { %>
  190. // Record store mutations for full-static after nuxtServerInit and Middleware
  191. ssrContext.nuxt.mutations =[]
  192. ssrContext.unsetMutationObserver = store.subscribe(m => { ssrContext.nuxt.mutations.push([m.type, m.payload]) })
  193. <% } %>
  194. <% if (features.layouts) { %>
  195. /*
  196. ** Set layout
  197. */
  198. let layout = Components.length ? Components[0].options.layout : NuxtError.layout
  199. if (typeof layout === 'function') {
  200. layout = layout(app.context)
  201. }
  202. await _app.loadLayout(layout)
  203. if (ssrContext.nuxt.error) {
  204. return renderErrorPage()
  205. }
  206. layout = _app.setLayout(layout)
  207. ssrContext.nuxt.layout = _app.layoutName
  208. <% } %>
  209. <% if (features.middleware) { %>
  210. /*
  211. ** Call middleware (layout + pages)
  212. */
  213. midd = []
  214. <% if (features.layouts) { %>
  215. layout = sanitizeComponent(layout)
  216. if (layout.options.middleware) {
  217. midd = midd.concat(layout.options.middleware)
  218. }
  219. <% } %>
  220. Components.forEach((Component) => {
  221. if (Component.options.middleware) {
  222. midd = midd.concat(Component.options.middleware)
  223. }
  224. })
  225. midd = midd.map((name) => {
  226. if (typeof name === 'function') {
  227. return name
  228. }
  229. if (typeof middleware[name] !== 'function') {
  230. app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
  231. }
  232. return middleware[name]
  233. })
  234. await middlewareSeries(midd, app.context)
  235. // ...If there is a redirect or an error, stop the process
  236. if (ssrContext.redirected) {
  237. return noopApp()
  238. }
  239. if (ssrContext.nuxt.error) {
  240. return renderErrorPage()
  241. }
  242. <% } %>
  243. <% if (features.validate) { %>
  244. /*
  245. ** Call .validate()
  246. */
  247. let isValid = true
  248. try {
  249. for (const Component of Components) {
  250. if (typeof Component.options.validate !== 'function') {
  251. continue
  252. }
  253. isValid = await Component.options.validate(app.context)
  254. if (!isValid) {
  255. break
  256. }
  257. }
  258. } catch (validationError) {
  259. // ...If .validate() threw an error
  260. app.context.error({
  261. statusCode: validationError.statusCode || '500',
  262. message: validationError.message
  263. })
  264. return renderErrorPage()
  265. }
  266. // ...If .validate() returned false
  267. if (!isValid) {
  268. // Render a 404 error page
  269. return render404Page()
  270. }
  271. <% } %>
  272. // If no Components found, returns 404
  273. if (!Components.length) {
  274. return render404Page()
  275. }
  276. <% if (features.asyncData || features.fetch) { %>
  277. // Call asyncData & fetch hooks on components matched by the route.
  278. const asyncDatas = await Promise.all(Components.map((Component) => {
  279. const promises = []
  280. <% if (features.asyncData) { %>
  281. // Call asyncData(context)
  282. if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
  283. const promise = promisify(Component.options.asyncData, app.context)
  284. promise.then((asyncDataResult) => {
  285. ssrContext.asyncData[Component.cid] = asyncDataResult
  286. applyAsyncData(Component)
  287. return asyncDataResult
  288. })
  289. promises.push(promise)
  290. } else {
  291. promises.push(null)
  292. }
  293. <% } %>
  294. <% if (features.fetch) { %>
  295. // Call fetch(context)
  296. if (Component.options.fetch && Component.options.fetch.length) {
  297. promises.push(Component.options.fetch(app.context))
  298. } else {
  299. promises.push(null)
  300. }
  301. <% } %>
  302. return Promise.all(promises)
  303. }))
  304. <% if (debug) { %>if (process.env.DEBUG && asyncDatas.length) console.debug('Data fetching ' + ssrContext.url + ': ' + (Date.now() - s) + 'ms')<% } %>
  305. // datas are the first row of each
  306. ssrContext.nuxt.data = asyncDatas.map(r => r[0] || {})
  307. <% } %>
  308. // ...If there is a redirect or an error, stop the process
  309. if (ssrContext.redirected) {
  310. return noopApp()
  311. }
  312. if (ssrContext.nuxt.error) {
  313. return renderErrorPage()
  314. }
  315. // Call beforeNuxtRender methods & add store state
  316. await beforeRender()
  317. return _app
  318. }