Repository: nuxt/nuxt.js
Pull request #7633 (2020-06-29 08:36:48)

feat(vue-renderer): add `vue-renderer:spa:prepareContext` hook
## Types of changes
- [ ] Bug fix (a non-breaking change which fixes an issue)
- [x] New feature (a non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)


## Description

For SPA and static site builds, the generated HTML pages all use the meta options from `nuxt.config.js`. This is a problem for SEO (obviously) and for link sharing where the meta content of the HTML will be parsed to give a preview. While a universal or server-sided rendered app would solve this, I'd rather not have to deal with the complexities and additional build time when there is a fairly simple solution (I only need the meta pre-rendered, nothing else).

What I'm proposing is a hook for vue-meta allowing the app to decide on a more granular level what meta to use for each route. I believe this is in-line with existing core functionality and could open up other use cases.

I would hope one day the vue-meta plugin could just work for this for static / spa built sites, reading the page component meta, but I think this can be a good intermediate solution.

Example: with this code block a user can generate all their static HTML to have individual titles without any effort 

```
hooks: {
    'vue-renderer': {
      spa: {
        meta ({ head, renderContext }) {
          const urlParts = renderContext.url.split('/')
          head.title = startCase(urlParts.pop())
        }
      }
    },
  },
```

## Checklist:
<!--- Put an `x` in all the boxes that apply. -->
<!--- If your change requires a documentation PR, please link it appropriately -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My change requires a change to the documentation. - **No documentation page for `vue-renderer` hooks that I can see?**
- [ ] I have updated the documentation accordingly. (PR: #)
- [ ] I have added tests to cover my changes (if not applicable, please state why) - **Wasn't sure exactly where to put these tests, wasn't a SPA generate unit test from what I could see?**
- [ ] All new and existing tests are passing.



codecov-commenter: # [Codecov](https://codecov.io/gh/nuxt/nuxt.js/pull/7633?src=pr&el=h1) Report
> Merging [#7633](https://codecov.io/gh/nuxt/nuxt.js/pull/7633?src=pr&el=desc) into [dev](https://codecov.io/gh/nuxt/nuxt.js/commit/c998072c94eef659d45819eaa2b18897b8923538&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `0.00%`.

[![Impacted file tree graph](https://codecov.io/gh/nuxt/nuxt.js/pull/7633/graphs/tree.svg?width=650&height=150&src=pr&token=nGD1PtMB3M)](https://codecov.io/gh/nuxt/nuxt.js/pull/7633?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##              dev    #7633      +/-   ##
==========================================
- Coverage   70.28%   70.26%   -0.02%     
==========================================
  Files          88       88              
  Lines        3749     3750       +1     
  Branches     1020     1020              
==========================================
  Hits         2635     2635              
- Misses        905      906       +1     
  Partials      209      209              
```

| Flag | Coverage Δ | |
|---|---|---|
| #unittests | `70.26% <0.00%> (-0.02%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/nuxt/nuxt.js/pull/7633?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [packages/vue-renderer/src/renderers/spa.js](https://codecov.io/gh/nuxt/nuxt.js/pull/7633/diff?src=pr&el=tree#diff-cGFja2FnZXMvdnVlLXJlbmRlcmVyL3NyYy9yZW5kZXJlcnMvc3BhLmpz) | `0.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/nuxt/nuxt.js/pull/7633?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/nuxt/nuxt.js/pull/7633?src=pr&el=footer). Last update [c998072...73dcb17](https://codecov.io/gh/nuxt/nuxt.js/pull/7633?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/packages/utils/src/route.js b/packages/utils/src/route.js
index 95dc8f238..cc619c187 100644
--- a/packages/utils/src/route.js
+++ b/packages/utils/src/route.js
@@ -30,7 +30,7 @@ export const flatRoutes = function flatRoutes (router, fileName = '', routes = [
   return routes
 }
 
-function cleanChildrenRoutes (routes, isChild = false, routeNameSplitter = '-') {
+function cleanChildrenRoutes (routes, isChild = false, routeNameSplitter = '-', trailingSlash) {
   let start = -1
   const regExpIndex = new RegExp(`${routeNameSplitter}index$`)
   const routesIndex = []
@@ -68,10 +68,15 @@ function cleanChildrenRoutes (routes, isChild = false, routeNameSplitter = '-')
     }
     route.name = route.name.replace(regExpIndex, '')
     if (route.children) {
-      if (route.children.find(child => child.path === '')) {
+      const indexRoutePath = trailingSlash === false ? '/' : ''
+      const defaultChildRoute = route.children.find(child => child.path === indexRoutePath)
+      if (defaultChildRoute) {
+        if (trailingSlash === false) {
+          defaultChildRoute.name = route.name
+        }
         delete route.name
       }
-      route.children = cleanChildrenRoutes(route.children, true, routeNameSplitter)
+      route.children = cleanChildrenRoutes(route.children, true, routeNameSplitter, trailingSlash)
     }
   })
   return routes
@@ -188,7 +193,7 @@ export const createRoutes = function createRoutes ({
   })
 
   sortRoutes(routes)
-  return cleanChildrenRoutes(routes, false, routeNameSplitter)
+  return cleanChildrenRoutes(routes, false, routeNameSplitter, trailingSlash)
 }
 
 // Guard dir1 from dir2 which can be indiscriminately removed
diff --git a/packages/vue-renderer/src/renderers/spa.js b/packages/vue-renderer/src/renderers/spa.js
index bc4ef95e7..8ec5446a4 100644
--- a/packages/vue-renderer/src/renderers/spa.js
+++ b/packages/vue-renderer/src/renderers/spa.js
@@ -51,14 +51,16 @@ export default class SPARenderer extends BaseRenderer {
 
     if (this.options.features.meta) {
       // Get vue-meta context
-      let head
-      if (typeof this.options.head === 'function') {
-        head = this.options.head()
-      } else {
-        head = cloneDeep(this.options.head)
-      }
+      renderContext.head = typeof this.options.head === 'function'
+        ? this.options.head()
+        : cloneDeep(this.options.head)
+    }
 
-      const m = VueMeta.generate(head || {}, this.vueMetaConfig)
+    // Allow overriding renderContext
+    await this.serverContext.nuxt.callHook('vue-renderer:spa:prepareContext', renderContext)
+
+    if (this.options.features.meta) {
+      const m = VueMeta.generate(renderContext.head || {}, this.vueMetaConfig)
 
       // HTML_ATTRS
       meta.HTML_ATTRS = m.htmlAttrs.text()


Test patch:
diff --git a/packages/utils/test/__snapshots__/route.test.js.snap b/packages/utils/test/__snapshots__/route.test.js.snap
index 1285e2b64..9731bddd9 100644
--- a/packages/utils/test/__snapshots__/route.test.js.snap
+++ b/packages/utils/test/__snapshots__/route.test.js.snap
@@ -260,7 +260,6 @@ Array [
     ],
     "chunkName": "pages/another_route/_id",
     "component": "/some/nuxt/app/pages/another_route/_id.vue",
-    "name": "another_route-id",
     "path": "/another_route/:id?",
     "pathToRegexpOptions": Object {
       "strict": true,