Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

Commit

Permalink
feat(nuxt): add setPageLayout utility (#6826) (#7075)
Browse files Browse the repository at this point in the history
Co-authored-by: HomWang <[email protected]>
  • Loading branch information
danielroe and HomWang authored Aug 31, 2022
1 parent c92b6a0 commit b90d286
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 6 deletions.
3 changes: 1 addition & 2 deletions docs/content/2.guide/3.directory-structure/7.layouts.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,8 @@ You can also use a ref or computed property for your layout.
</template>
<script setup>
const route = useRoute()
function enableCustomLayout () {
route.meta.layout = "custom"
setPageLayout('custom')
}
definePageMeta({
layout: false,
Expand Down
14 changes: 14 additions & 0 deletions docs/content/3.api/1.composables/set-layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# `setPageLayout`

`setPageLayout` allows you to dynamically change the layout of a page. It relies on access to the Nuxt context and can only be called within components' setup functions, plugins, and route middleware.

```ts
export default defineNuxtRouteMiddleware((to) => {
// Set the layout on the route you are navigating _to_
setPageLayout('other')
})
```

::alert{icon=👉}
If you choose to set the layout dynamically on the server side, you _must_ do so before the layout is rendered by Vue (that is, within a plugin or route middleware) to avoid a hydration mismatch.
::
4 changes: 4 additions & 0 deletions examples/routing/layouts/middleware/other.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default defineNuxtRouteMiddleware(() => {
setPageLayout('other')
})
12 changes: 12 additions & 0 deletions examples/routing/layouts/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@
<NuxtLink to="/dynamic">
Dynamic layout
</NuxtLink>
<NuxtLink to="/other">
Other layout
</NuxtLink>
<NButton @click="setPageLayout('default')">
Change to default layout
</NButton>
<NButton @click="setPageLayout('custom')">
Change to custom layout
</NButton>
<NButton @click="setPageLayout('other')">
Change to other layout
</NButton>
</nav>
</template>
</NuxtExampleLayout>
Expand Down
13 changes: 13 additions & 0 deletions examples/routing/layouts/pages/other.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script setup>
definePageMeta({
middleware: 'other'
})
</script>

<template>
<div>
<NuxtLink to="/">
Back to home
</NuxtLink>
</div>
</template>
2 changes: 1 addition & 1 deletion packages/nuxt/src/app/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ export type { FetchResult, UseFetchOptions } from './fetch'
export { useCookie } from './cookie'
export type { CookieOptions, CookieRef } from './cookie'
export { useRequestHeaders, useRequestEvent, setResponseStatus } from './ssr'
export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, navigateTo, useRoute, useActiveRoute, useRouter } from './router'
export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, setPageLayout, navigateTo, useRoute, useActiveRoute, useRouter } from './router'
export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router'
export { preloadComponents, prefetchComponents } from './preload'
19 changes: 18 additions & 1 deletion packages/nuxt/src/app/composables/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getCurrentInstance, inject } from 'vue'
import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw, NavigationFailure, RouteLocationPathRaw } from 'vue-router'
import { sendRedirect } from 'h3'
import { hasProtocol, joinURL, parseURL } from 'ufo'
import { useNuxtApp, useRuntimeConfig } from '#app'
import { useNuxtApp, useRuntimeConfig, useState } from '#app'

export const useRouter = () => {
return useNuxtApp()?.$router as Router
Expand Down Expand Up @@ -114,3 +114,20 @@ export const abortNavigation = (err?: Error | string) => {
}
return false
}

export const setPageLayout = (layout: string) => {
if (process.server) {
useState('_layout').value = layout
}
const nuxtApp = useNuxtApp()
const inMiddleware = isProcessingMiddleware()
if (inMiddleware || process.server || nuxtApp.isHydrating) {
const unsubscribe = useRouter().beforeResolve((to) => {
to.meta.layout = layout
unsubscribe()
})
}
if (!inMiddleware) {
useRoute().meta.layout = layout
}
}
6 changes: 5 additions & 1 deletion packages/nuxt/src/app/plugins/router.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { reactive, h } from 'vue'
import { parseURL, stringifyParsedURL, parseQuery, stringifyQuery, withoutBase, isEqual, joinURL } from 'ufo'
import { createError } from 'h3'
import { defineNuxtPlugin, clearError, navigateTo, showError, useRuntimeConfig } from '..'
import { defineNuxtPlugin, clearError, navigateTo, showError, useRuntimeConfig, useState } from '..'
import { callWithNuxt } from '../nuxt'
// @ts-ignore
import { globalMiddleware } from '#build/middleware'
Expand Down Expand Up @@ -218,9 +218,13 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
named: {}
}

const initialLayout = useState('_layout')
nuxtApp.hooks.hookOnce('app:created', async () => {
router.beforeEach(async (to, from) => {
to.meta = reactive(to.meta || {})
if (nuxtApp.isHydrating) {
to.meta.layout = initialLayout.value ?? to.meta.layout
}
nuxtApp._processingMiddleware = true

const middlewareEntries = new Set<RouteGuard>([...globalMiddleware, ...nuxtApp._middleware.global])
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt/src/imports/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const appPreset = defineUnimportPreset({
'useRequestHeaders',
'useRequestEvent',
'setResponseStatus',
'setPageLayout',
'useRouter',
'useRoute',
'useActiveRoute',
Expand Down
6 changes: 5 additions & 1 deletion packages/nuxt/src/pages/runtime/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { createError } from 'h3'
import { withoutBase, isEqual } from 'ufo'
import NuxtPage from './page'
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig, showError, clearError, navigateTo, useError } from '#app'
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig, showError, clearError, navigateTo, useError, useState } from '#app'
// @ts-ignore
import routes from '#build/routes'
// @ts-ignore
Expand Down Expand Up @@ -114,8 +114,12 @@ export default defineNuxtPlugin(async (nuxtApp) => {
callWithNuxt(nuxtApp, showError, [error])
}

const initialLayout = useState('_layout')
router.beforeEach(async (to, from) => {
to.meta = reactive(to.meta)
if (nuxtApp.isHydrating) {
to.meta.layout = initialLayout.value ?? to.meta.layout
}
nuxtApp._processingMiddleware = true

type MiddlewareDef = string | NavigationGuard
Expand Down
10 changes: 10 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,16 @@ describe('layouts', () => {
expect(html).toContain('with-layout.vue')
expect(html).toContain('Custom Layout:')
})
it('should work with a dynamically set layout', async () => {
const html = await $fetch('/with-dynamic-layout')

// Snapshot
// expect(html).toMatchInlineSnapshot()

expect(html).toContain('with-dynamic-layout')
expect(html).toContain('Custom Layout:')
await expectNoClientErrors('/with-dynamic-layout')
})
})

describe('reactivity transform', () => {
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/basic/middleware/sets-layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default defineNuxtRouteMiddleware(async () => {
await new Promise(resolve => setTimeout(resolve, 10))
setPageLayout('custom')
})
11 changes: 11 additions & 0 deletions test/fixtures/basic/pages/with-dynamic-layout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup>
definePageMeta({
middleware: 'sets-layout'
})
</script>

<template>
<div>
<div>with-dynamic-layout.vue</div>
</div>
</template>

0 comments on commit b90d286

Please sign in to comment.