mirror of
https://github.com/IT4Change/boilerplate-frontend.git
synced 2025-12-13 07:35:53 +00:00
per-page-title
This commit is contained in:
parent
335de9753b
commit
5fd3b4f7ad
@ -1,3 +1,5 @@
|
|||||||
# META
|
# META
|
||||||
PUBLIC_ENV__META__DEFAULT_TITLE="IT4C"
|
PUBLIC_ENV__META__BASE_URL="http://localhost:3000"
|
||||||
PUBLIC_ENV__META__DEFAULT_DESCRIPTION="IT4C Frontend Boilerplate"
|
PUBLIC_ENV__META__DEFAULT_AUTHOR="IT Team 4 Change"
|
||||||
|
PUBLIC_ENV__META__DEFAULT_DESCRIPTION="IT4C Frontend Boilerplate"
|
||||||
|
PUBLIC_ENV__META__DEFAULT_TITLE="IT4C"
|
||||||
@ -3,4 +3,14 @@ export default {
|
|||||||
clientRouting: true,
|
clientRouting: true,
|
||||||
prefetchStaticAssets: 'viewport',
|
prefetchStaticAssets: 'viewport',
|
||||||
passToClient: ['pageProps', /* 'urlPathname', */ 'routeParams'],
|
passToClient: ['pageProps', /* 'urlPathname', */ 'routeParams'],
|
||||||
|
meta: {
|
||||||
|
title: {
|
||||||
|
// Make the value of `title` available on both the server- and client-side
|
||||||
|
env: { server: true, client: true },
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
// Make the value of `description` available only on the server-side
|
||||||
|
env: { server: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,18 @@
|
|||||||
import { createApp } from './app'
|
import { PageContext } from 'vike/types'
|
||||||
|
|
||||||
import type { PageContext, VikePageContext } from '#types/PageContext'
|
import { createApp } from './app'
|
||||||
|
import { getTitle } from './utils'
|
||||||
|
|
||||||
let instance: ReturnType<typeof createApp>
|
let instance: ReturnType<typeof createApp>
|
||||||
/* async */ function render(pageContext: VikePageContext & PageContext) {
|
/* async */ function render(pageContext: PageContext) {
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
instance = createApp(pageContext)
|
instance = createApp(pageContext)
|
||||||
instance.app.mount('#app')
|
instance.app.mount('#app')
|
||||||
} else {
|
} else {
|
||||||
instance.app.changePage(pageContext)
|
instance.app.changePage(pageContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.title = getTitle(pageContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default render
|
export default render
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
import { renderToString as renderToString_ } from '@vue/server-renderer'
|
import { renderToString as renderToString_ } from '@vue/server-renderer'
|
||||||
import { escapeInject, dangerouslySkipEscape } from 'vike/server'
|
import { escapeInject, dangerouslySkipEscape } from 'vike/server'
|
||||||
|
import { PageContext, PageContextServer } from 'vike/types'
|
||||||
|
|
||||||
import logoUrl from '#assets/favicon.ico'
|
import logoUrl from '#assets/favicon.ico'
|
||||||
|
import image from '#assets/it4c-logo2-clean-bg_alpha-128x128.png'
|
||||||
import { META } from '#src/env'
|
import { META } from '#src/env'
|
||||||
|
|
||||||
import { createApp } from './app'
|
|
||||||
|
|
||||||
import type { PageContextServer, PageContext } from '#types/PageContext'
|
import { createApp } from './app'
|
||||||
|
import { getDescription, getTitle } from './utils'
|
||||||
|
|
||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
|
|
||||||
async function render(pageContext: PageContextServer & PageContext) {
|
async function render(pageContext: PageContextServer & PageContext) {
|
||||||
@ -17,9 +20,8 @@ async function render(pageContext: PageContextServer & PageContext) {
|
|||||||
const appHtml = await renderToString(app)
|
const appHtml = await renderToString(app)
|
||||||
|
|
||||||
// See https://vike.dev/head
|
// See https://vike.dev/head
|
||||||
const { documentProps } = pageContext.exports
|
const title = getTitle(pageContext)
|
||||||
const title = (documentProps && documentProps.title) || META.DEFAULT_TITLE
|
const description = getDescription(pageContext)
|
||||||
const desc = (documentProps && documentProps.description) || META.DEFAULT_DESCRIPTION
|
|
||||||
|
|
||||||
const documentHtml = escapeInject`<!DOCTYPE html>
|
const documentHtml = escapeInject`<!DOCTYPE html>
|
||||||
<html lang="${locale}">
|
<html lang="${locale}">
|
||||||
@ -27,7 +29,23 @@ async function render(pageContext: PageContextServer & PageContext) {
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="${logoUrl}" />
|
<link rel="icon" href="${logoUrl}" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="${desc}" />
|
<meta name="description" content="${description}" />
|
||||||
|
<meta name="author" content="${META.DEFAULT_AUTHOR}">
|
||||||
|
<meta property="og:title" content="${title}" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="${META.BASE_URL}" />
|
||||||
|
<meta property="og:description" content="${description}" />
|
||||||
|
<meta property="og:image" content="${META.BASE_URL}${image}" />
|
||||||
|
<meta property="og:image:alt" content="${title}" />
|
||||||
|
<meta property="og:image:width" content="1200"/>
|
||||||
|
<meta property="og:image:height" content="601"/>
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<!--<meta name="twitter:site" content="@YourTwitterUsername" />-->
|
||||||
|
<meta name="twitter:title" content="${title}" />
|
||||||
|
<meta name="twitter:text:title" content="${title}" />
|
||||||
|
<meta name="twitter:description" content="${description}" />
|
||||||
|
<meta name="twitter:image" content="${META.BASE_URL}${image}" />
|
||||||
|
<meta name="twitter:image:alt" content="${title}" />
|
||||||
<title>${title}</title>
|
<title>${title}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -7,11 +7,11 @@ import i18n from '#plugins/i18n'
|
|||||||
import pinia from '#plugins/pinia'
|
import pinia from '#plugins/pinia'
|
||||||
import CreateVuetify from '#plugins/vuetify'
|
import CreateVuetify from '#plugins/vuetify'
|
||||||
|
|
||||||
import type { PageContext, VikePageContext } from '#types/PageContext'
|
import { PageContext } from 'vike/types'
|
||||||
|
|
||||||
const vuetify = CreateVuetify(i18n)
|
const vuetify = CreateVuetify(i18n)
|
||||||
|
|
||||||
function createApp(pageContext: VikePageContext & PageContext, isClient = true) {
|
function createApp(pageContext: PageContext, isClient = true) {
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
let rootComponent: InstanceType<typeof PageWithWrapper>
|
let rootComponent: InstanceType<typeof PageWithWrapper>
|
||||||
const PageWithWrapper = defineComponent({
|
const PageWithWrapper = defineComponent({
|
||||||
@ -47,7 +47,7 @@ function createApp(pageContext: VikePageContext & PageContext, isClient = true)
|
|||||||
app.use(vuetify)
|
app.use(vuetify)
|
||||||
|
|
||||||
objectAssign(app, {
|
objectAssign(app, {
|
||||||
changePage: (pageContext: VikePageContext & PageContext) => {
|
changePage: (pageContext: PageContext) => {
|
||||||
Object.assign(pageContextReactive, pageContext)
|
Object.assign(pageContextReactive, pageContext)
|
||||||
rootComponent.Page = markRaw(pageContext.Page)
|
rootComponent.Page = markRaw(pageContext.Page)
|
||||||
rootComponent.pageProps = markRaw(pageContext.pageProps || {})
|
rootComponent.pageProps = markRaw(pageContext.pageProps || {})
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
// `usePageContext` allows us to access `pageContext` in any Vue component.
|
// `usePageContext` allows us to access `pageContext` in any Vue component.
|
||||||
// See https://vike.dev/pageContext-anywhere
|
// See https://vike.dev/pageContext-anywhere
|
||||||
|
|
||||||
|
import { PageContext } from 'vike/types'
|
||||||
import { inject } from 'vue'
|
import { inject } from 'vue'
|
||||||
|
|
||||||
import { PageContext, VikePageContext } from '#types/PageContext'
|
|
||||||
|
|
||||||
import type { App, InjectionKey } from 'vue'
|
import type { App, InjectionKey } from 'vue'
|
||||||
|
|
||||||
export const vikePageContext: InjectionKey<VikePageContext & PageContext> = Symbol('pageContext')
|
export const vikePageContext: InjectionKey<PageContext> = Symbol('pageContext')
|
||||||
|
|
||||||
function usePageContext() {
|
function usePageContext() {
|
||||||
const pageContext = inject(vikePageContext)
|
const pageContext = inject(vikePageContext)
|
||||||
@ -15,7 +14,7 @@ function usePageContext() {
|
|||||||
return pageContext
|
return pageContext
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPageContext(app: App, pageContext: VikePageContext & PageContext) {
|
function setPageContext(app: App, pageContext: PageContext) {
|
||||||
app.provide(vikePageContext, pageContext)
|
app.provide(vikePageContext, pageContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
renderer/utils.ts
Normal file
19
renderer/utils.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { PageContext } from 'vike/types'
|
||||||
|
|
||||||
|
import { META } from '#src/env'
|
||||||
|
|
||||||
|
function getTitle(pageContext: PageContext) {
|
||||||
|
// The value exported by /pages/**/+title.js is available at pageContext.config.title
|
||||||
|
const val = pageContext.config.title
|
||||||
|
if (typeof val === 'string') return val
|
||||||
|
if (typeof val === 'function') return String(val(pageContext))
|
||||||
|
return META.DEFAULT_TITLE
|
||||||
|
}
|
||||||
|
function getDescription(pageContext: PageContext) {
|
||||||
|
const val = pageContext.config.description
|
||||||
|
if (typeof val === 'string') return val
|
||||||
|
if (typeof val === 'function') return val(pageContext)
|
||||||
|
return META.DEFAULT_DESCRIPTION
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getTitle, getDescription }
|
||||||
@ -5,8 +5,10 @@ import { META } from './env'
|
|||||||
describe('env', () => {
|
describe('env', () => {
|
||||||
it('has correct default values', () => {
|
it('has correct default values', () => {
|
||||||
expect(META).toEqual({
|
expect(META).toEqual({
|
||||||
DEFAULT_TITLE: 'IT4C',
|
BASE_URL: 'http://localhost:3000',
|
||||||
|
DEFAULT_AUTHOR: 'IT Team 4 Change',
|
||||||
DEFAULT_DESCRIPTION: 'IT4C Frontend Boilerplate',
|
DEFAULT_DESCRIPTION: 'IT4C Frontend Boilerplate',
|
||||||
|
DEFAULT_TITLE: 'IT4C',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
10
src/env.ts
10
src/env.ts
@ -1,8 +1,10 @@
|
|||||||
const META = {
|
const META = {
|
||||||
DEFAULT_TITLE: (import.meta.env.PUBLIC_ENV__META__DEFAULT_TITLE as string) ?? 'IT4C',
|
BASE_URL: (import.meta.env.PUBLIC_ENV__META__BASE_URL ?? 'http://localhost:3000') as string,
|
||||||
DEFAULT_DESCRIPTION:
|
DEFAULT_AUTHOR: (import.meta.env.PUBLIC_ENV__META__DEFAULT_AUTHOR ??
|
||||||
(import.meta.env.PUBLIC_ENV__META__DEFAULT_DESCRIPTION as string) ??
|
'IT Team 4 Change') as string,
|
||||||
'IT4C Frontend Boilerplate',
|
DEFAULT_DESCRIPTION: (import.meta.env.PUBLIC_ENV__META__DEFAULT_DESCRIPTION ??
|
||||||
|
'IT4C Frontend Boilerplate') as string,
|
||||||
|
DEFAULT_TITLE: (import.meta.env.PUBLIC_ENV__META__DEFAULT_TITLE ?? 'IT4C') as string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export { META }
|
export { META }
|
||||||
|
|||||||
1
src/pages/_error/+title.ts
Normal file
1
src/pages/_error/+title.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const title = 'IT4C | Error'
|
||||||
@ -1,49 +1,74 @@
|
|||||||
import { VueWrapper, mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import { describe, it, expect, beforeEach } from 'vitest'
|
import { describe, it, expect, beforeEach } from 'vitest'
|
||||||
import { ComponentPublicInstance } from 'vue'
|
import { Component, h } from 'vue'
|
||||||
|
import { VApp } from 'vuetify/components'
|
||||||
|
|
||||||
import ErrorPage from './+Page.vue'
|
import ErrorPage from './+Page.vue'
|
||||||
|
import { title } from './+title'
|
||||||
|
|
||||||
describe('ErrorPage', () => {
|
describe('ErrorPage', () => {
|
||||||
let wrapper: VueWrapper<unknown, ComponentPublicInstance<unknown, Omit<unknown, never>>>
|
it('title returns correct title', () => {
|
||||||
const Wrapper = () => {
|
expect(title).toBe('DreamMall | Fehler')
|
||||||
return mount(ErrorPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
})
|
||||||
|
describe('500 Error', () => {
|
||||||
|
const WrapperUndefined = () => {
|
||||||
|
return mount(VApp, {
|
||||||
|
slots: {
|
||||||
|
default: h(ErrorPage as Component),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const WrapperFalse = () => {
|
||||||
|
return mount(VApp, {
|
||||||
|
slots: {
|
||||||
|
default: h(ErrorPage as Component, {
|
||||||
|
is404: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
describe('no is404 property set', () => {
|
let wrapper: ReturnType<typeof WrapperUndefined>
|
||||||
it('renders error 500', () => {
|
beforeEach(() => {
|
||||||
expect(wrapper.find('h1').text()).toEqual("$t('error.500.h1')")
|
wrapper = WrapperUndefined()
|
||||||
expect(wrapper.find('p').text()).toEqual("$t('error.500.text')")
|
|
||||||
})
|
})
|
||||||
})
|
describe('no is404 property set', () => {
|
||||||
|
it('renders error 500', () => {
|
||||||
describe('is404 property is false', () => {
|
expect(wrapper.find('h1').text()).toEqual("$t('error.500.h1')")
|
||||||
beforeEach(async () => {
|
expect(wrapper.find('p').text()).toEqual("$t('error.500.text')")
|
||||||
await wrapper.setProps({
|
|
||||||
is404: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders error 500', () => {
|
describe('is404 property is false', () => {
|
||||||
expect(wrapper.find('h1').text()).toEqual("$t('error.500.h1')")
|
beforeEach(() => {
|
||||||
expect(wrapper.find('p').text()).toEqual("$t('error.500.text')")
|
wrapper = WrapperFalse()
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
describe('is404 property is true', () => {
|
it('renders error 500', () => {
|
||||||
beforeEach(async () => {
|
expect(wrapper.find('h1').text()).toEqual("$t('error.500.h1')")
|
||||||
await wrapper.setProps({
|
expect(wrapper.find('p').text()).toEqual("$t('error.500.text')")
|
||||||
is404: true,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
it('renders error 400', () => {
|
describe('404 Error', () => {
|
||||||
expect(wrapper.find('h1').text()).toEqual("$t('error.404.h1')")
|
const Wrapper = () => {
|
||||||
expect(wrapper.find('p').text()).toEqual("$t('error.404.text')")
|
return mount(VApp, {
|
||||||
|
slots: {
|
||||||
|
default: h(ErrorPage as Component, {
|
||||||
|
is404: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let wrapper: ReturnType<typeof Wrapper>
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
describe('is404 property is true', () => {
|
||||||
|
it('renders error 400', () => {
|
||||||
|
expect(wrapper.find('h1').text()).toEqual("$t('error.404.h1')")
|
||||||
|
expect(wrapper.find('p').text()).toEqual("$t('error.404.text')")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
1
src/pages/about/+title.ts
Normal file
1
src/pages/about/+title.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const title = 'IT4C | About'
|
||||||
@ -4,6 +4,7 @@ import { Component, h } from 'vue'
|
|||||||
import { VApp } from 'vuetify/components'
|
import { VApp } from 'vuetify/components'
|
||||||
|
|
||||||
import AboutPage from './+Page.vue'
|
import AboutPage from './+Page.vue'
|
||||||
|
import { title } from './+title'
|
||||||
|
|
||||||
describe('AboutPage', () => {
|
describe('AboutPage', () => {
|
||||||
const wrapper = mount(VApp, {
|
const wrapper = mount(VApp, {
|
||||||
@ -12,6 +13,10 @@ describe('AboutPage', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('title returns correct title', () => {
|
||||||
|
expect(title).toBe('DreamMall')
|
||||||
|
})
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(wrapper.element).toMatchSnapshot()
|
expect(wrapper.element).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { resolveRoute } from 'vike/routing'
|
import { resolveRoute } from 'vike/routing'
|
||||||
|
import { PageContext } from 'vike/types'
|
||||||
import { PageContext } from '#types/PageContext'
|
|
||||||
|
|
||||||
export default (pageContext: PageContext) => {
|
export default (pageContext: PageContext) => {
|
||||||
{
|
{
|
||||||
|
|||||||
1
src/pages/app/+title.ts
Normal file
1
src/pages/app/+title.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const title = 'IT4C | App'
|
||||||
@ -4,6 +4,7 @@ import { Component, h } from 'vue'
|
|||||||
import { VApp } from 'vuetify/components'
|
import { VApp } from 'vuetify/components'
|
||||||
|
|
||||||
import AppPage from './+Page.vue'
|
import AppPage from './+Page.vue'
|
||||||
|
import { title } from './+title'
|
||||||
|
|
||||||
describe('AppPage', () => {
|
describe('AppPage', () => {
|
||||||
const wrapper = mount(VApp, {
|
const wrapper = mount(VApp, {
|
||||||
@ -12,6 +13,10 @@ describe('AppPage', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('title returns correct title', () => {
|
||||||
|
expect(title).toBe('DreamMall')
|
||||||
|
})
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(wrapper.element).toMatchSnapshot()
|
expect(wrapper.element).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|||||||
3
src/pages/index/+title.ts
Normal file
3
src/pages/index/+title.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { META } from '#src/env'
|
||||||
|
|
||||||
|
export const title = META.DEFAULT_TITLE
|
||||||
@ -4,14 +4,19 @@ import { Component, h } from 'vue'
|
|||||||
import { VApp } from 'vuetify/components'
|
import { VApp } from 'vuetify/components'
|
||||||
|
|
||||||
import IndexPage from './+Page.vue'
|
import IndexPage from './+Page.vue'
|
||||||
|
import { title } from './+title'
|
||||||
|
|
||||||
describe('DataPrivacyPage', () => {
|
describe('IndexPage', () => {
|
||||||
const wrapper = mount(VApp, {
|
const wrapper = mount(VApp, {
|
||||||
slots: {
|
slots: {
|
||||||
default: h(IndexPage as Component),
|
default: h(IndexPage as Component),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('title returns default title', () => {
|
||||||
|
expect(title).toBe('DreamMall')
|
||||||
|
})
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(wrapper.element).toMatchSnapshot()
|
expect(wrapper.element).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|||||||
16
types/PageContext.d.ts
vendored
Normal file
16
types/PageContext.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Page } from '#types/Page'
|
||||||
|
import { PageProps } from '#types/PageProps'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Vike {
|
||||||
|
interface PageContext {
|
||||||
|
urlPathname: string
|
||||||
|
config: {
|
||||||
|
title: string | ((pageContext: PageContext) => string) | undefined
|
||||||
|
description: string | ((pageContext: PageContext) => string) | undefined
|
||||||
|
}
|
||||||
|
Page: Page
|
||||||
|
pageProps?: PageProps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import { Page } from '#types/Page'
|
|
||||||
import { PageProps } from '#types/PageProps'
|
|
||||||
|
|
||||||
export type {
|
|
||||||
PageContextServer,
|
|
||||||
|
|
||||||
// When using Client Routing https://vike.dev/clientRouting
|
|
||||||
PageContextClient,
|
|
||||||
PageContext as VikePageContext,
|
|
||||||
PageContextBuiltInClientWithClientRouting as PageContextBuiltInClient,
|
|
||||||
// When using Server Routing
|
|
||||||
/*
|
|
||||||
PageContextClientWithServerRouting as PageContextClient,
|
|
||||||
PageContextWithServerRouting as PageContext,
|
|
||||||
*/
|
|
||||||
//* /
|
|
||||||
} from 'vike/types'
|
|
||||||
|
|
||||||
export type PageContext = {
|
|
||||||
Page: Page
|
|
||||||
pageProps?: PageProps
|
|
||||||
urlPathname: string
|
|
||||||
exports: {
|
|
||||||
documentProps?: {
|
|
||||||
title?: string
|
|
||||||
description?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
documentProps?: {
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user