-
+
+
) : (
copy
)}
diff --git a/docs/app/(site)/enterprise/page.tsx b/docs/app/(site)/enterprise/page.tsx
new file mode 100644
index 00000000000..81a361d2813
--- /dev/null
+++ b/docs/app/(site)/enterprise/page.tsx
@@ -0,0 +1,11 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'Keystone for Enterprise',
+ description:
+ 'Discover how Keystone’s Admin UI is a natural extension of how you work in code, and has the flexibility you need to enable content creatives.',
+}
+
+export default function ForOrganisations () {
+ return
+}
diff --git a/docs/pages/for-content-management.tsx b/docs/app/(site)/for-content-management/page-client.tsx
similarity index 90%
rename from docs/pages/for-content-management.tsx
rename to docs/app/(site)/for-content-management/page-client.tsx
index a1168e6c668..ed2041f4fdb 100644
--- a/docs/pages/for-content-management.tsx
+++ b/docs/app/(site)/for-content-management/page-client.tsx
@@ -1,39 +1,35 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import Image from 'next/image'
import Link from 'next/link'
-import { useMediaQuery } from '../lib/media'
-import { IntroWrapper, IntroHeading, IntroLead } from '../components/content/Intro'
-import { Highlight } from '../components/primitives/Highlight'
-import { MWrapper } from '../components/content/MWrapper'
-import { Section, SideBySideSection } from '../components/content/Section'
-import { Button } from '../components/primitives/Button'
-import { Quote } from '../components/content/Quote'
-import { Type } from '../components/primitives/Type'
-import { ArrowR } from '../components/icons/ArrowR'
-import { Pill } from '../components/content/Pill'
-import { Tick } from '../components/icons/Tick'
-import { Page } from '../components/Page'
+import { useMediaQuery } from '../../../lib/media'
+import { IntroWrapper, IntroHeading, IntroLead } from '../../../components/content/Intro'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { MWrapper } from '../../../components/content/MWrapper'
+import { Section, SideBySideSection } from '../../../components/content/Section'
+import { Button } from '../../../components/primitives/Button'
+import { Quote } from '../../../components/content/Quote'
+import { Type } from '../../../components/primitives/Type'
+import { ArrowR } from '../../../components/icons/ArrowR'
+import { Pill } from '../../../components/content/Pill'
+import { Tick } from '../../../components/icons/Tick'
+import { Page } from '../../../components/Page'
-import dsGeneration from '../public/assets/ds-generation.png'
-import contentManagement1 from '../public/assets/content-management-1.png'
-import contentManagement2 from '../public/assets/content-management-2.png'
-import contentManagement3 from '../public/assets/content-management-3.png'
-import contentManagement4 from '../public/assets/content-management-4.png'
-import { EndCta } from '../components/content/EndCta'
+import dsGeneration from '../../../public/assets/ds-generation.png'
+import contentManagement1 from '../../../public/assets/content-management-1.png'
+import contentManagement2 from '../../../public/assets/content-management-2.png'
+import contentManagement3 from '../../../public/assets/content-management-3.png'
+import contentManagement4 from '../../../public/assets/content-management-4.png'
+import { EndCta } from '../../../components/content/EndCta'
export default function ForOrganisations () {
const mq = useMediaQuery()
return (
-
+ Keystone for content management
diff --git a/docs/app/(site)/for-content-management/page.tsx b/docs/app/(site)/for-content-management/page.tsx
new file mode 100644
index 00000000000..29999fd0b78
--- /dev/null
+++ b/docs/app/(site)/for-content-management/page.tsx
@@ -0,0 +1,11 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'KeystoneJS for Content Management',
+ description:
+ 'Discover how Keystone’s Admin UI is a natural extension of how you work in code, and has the flexibility you need to enable content creatives.',
+}
+
+export default function ForOrganisations () {
+ return
+}
diff --git a/docs/pages/for-developers.tsx b/docs/app/(site)/for-developers/page-client.tsx
similarity index 90%
rename from docs/pages/for-developers.tsx
rename to docs/app/(site)/for-developers/page-client.tsx
index 8e4c1724451..c27772a88a4 100644
--- a/docs/pages/for-developers.tsx
+++ b/docs/app/(site)/for-developers/page-client.tsx
@@ -1,48 +1,44 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import Image from 'next/image'
import Link from 'next/link'
-import { useMediaQuery } from '../lib/media'
-import { IntroWrapper, IntroHeading, IntroLead } from '../components/content/Intro'
-import { CommunityCta } from '../components/content/CommunityCta'
-import { FrontEndLogos } from '../components/icons/FrontEndLogos'
-import { Highlight } from '../components/primitives/Highlight'
-import { Relational } from '../components/icons/Relational'
-import { TweetBox } from '../components/content/TweetBox'
-import { MWrapper } from '../components/content/MWrapper'
-import { Typescript } from '../components/icons/Typescript'
-import { Section, SideBySideSection } from '../components/content/Section'
-import { CodeBox } from '../components/content/CodeBox'
-import { Button } from '../components/primitives/Button'
-import { EndCta } from '../components/content/EndCta'
-import { Postgres } from '../components/icons/Postgres'
-import { Emoji } from '../components/primitives/Emoji'
-import { GraphQl } from '../components/icons/GraphQl'
-import { Type } from '../components/primitives/Type'
-import { ArrowR } from '../components/icons/ArrowR'
-import { Nextjs } from '../components/icons/Nextjs'
-import { Shield } from '../components/icons/Shield'
-import { Prisma } from '../components/icons/Prisma'
-import { Pill } from '../components/content/Pill'
-import { Tick } from '../components/icons/Tick'
-import { Cli } from '../components/icons/Cli'
-import { Page } from '../components/Page'
+import { useMediaQuery } from '../../../lib/media'
+import { IntroWrapper, IntroHeading, IntroLead } from '../../../components/content/Intro'
+import { CommunityCta } from '../../../components/content/CommunityCta'
+import { FrontEndLogos } from '../../../components/icons/FrontEndLogos'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { Relational } from '../../../components/icons/Relational'
+import { TweetBox } from '../../../components/content/TweetBox'
+import { MWrapper } from '../../../components/content/MWrapper'
+import { Typescript } from '../../../components/icons/Typescript'
+import { Section, SideBySideSection } from '../../../components/content/Section'
+import { CodeBox } from '../../../components/content/CodeBox'
+import { Button } from '../../../components/primitives/Button'
+import { EndCta } from '../../../components/content/EndCta'
+import { Postgres } from '../../../components/icons/Postgres'
+import { Emoji } from '../../../components/primitives/Emoji'
+import { GraphQl } from '../../../components/icons/GraphQl'
+import { Type } from '../../../components/primitives/Type'
+import { ArrowR } from '../../../components/icons/ArrowR'
+import { Nextjs } from '../../../components/icons/Nextjs'
+import { Shield } from '../../../components/icons/Shield'
+import { Prisma } from '../../../components/icons/Prisma'
+import { Pill } from '../../../components/content/Pill'
+import { Tick } from '../../../components/icons/Tick'
+import { Cli } from '../../../components/icons/Cli'
+import { Page } from '../../../components/Page'
-import editorStorytelling from '../public/assets/editor-storytelling.png'
-import richTextEditor from '../public/assets/rich-text-editor.png'
+import editorStorytelling from '../../../public/assets/editor-storytelling.png'
+import richTextEditor from '../../../public/assets/rich-text-editor.png'
export default function ForDevelopers () {
const mq = useMediaQuery()
return (
-
+ Keystone for developers
@@ -65,7 +61,7 @@ export default function ForDevelopers () {
marginTop: '2.5rem',
})}
>
-
+
+}
diff --git a/docs/pages/for-organisations.tsx b/docs/app/(site)/for-organisations/page-client.tsx
similarity index 90%
rename from docs/pages/for-organisations.tsx
rename to docs/app/(site)/for-organisations/page-client.tsx
index 198bfa580af..b332d8c8782 100644
--- a/docs/pages/for-organisations.tsx
+++ b/docs/app/(site)/for-organisations/page-client.tsx
@@ -1,37 +1,33 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import Image from 'next/image'
import Link from 'next/link'
-import { useMediaQuery } from '../lib/media'
-import { IntroWrapper, IntroHeading, IntroLead } from '../components/content/Intro'
-import { Highlight } from '../components/primitives/Highlight'
-import { ClientLogos } from '../components/icons/ClientLogos'
-import { TweetBox } from '../components/content/TweetBox'
-import { MWrapper } from '../components/content/MWrapper'
-import { Section } from '../components/content/Section'
-import { Thinkmill } from '../components/icons/Thinkmill'
-import { Quote } from '../components/content/Quote'
-import { Type } from '../components/primitives/Type'
-import { Pill } from '../components/content/Pill'
-import { Tick } from '../components/icons/Tick'
-import { Page } from '../components/Page'
+import { useMediaQuery } from '../../../lib/media'
+import { IntroWrapper, IntroHeading, IntroLead } from '../../../components/content/Intro'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { ClientLogos } from '../../../components/icons/ClientLogos'
+import { TweetBox } from '../../../components/content/TweetBox'
+import { MWrapper } from '../../../components/content/MWrapper'
+import { Section } from '../../../components/content/Section'
+import { Thinkmill } from '../../../components/icons/Thinkmill'
+import { Quote } from '../../../components/content/Quote'
+import { Type } from '../../../components/primitives/Type'
+import { Pill } from '../../../components/content/Pill'
+import { Tick } from '../../../components/icons/Tick'
+import { Page } from '../../../components/Page'
-import editor from '../public/assets/editor.png'
-import dataIntegrity from '../public/assets/data-integrity.png'
-import { EndCta } from '../components/content/EndCta'
+import editor from '../../../public/assets/editor.png'
+import dataIntegrity from '../../../public/assets/data-integrity.png'
+import { EndCta } from '../../../components/content/EndCta'
export default function ForOrganisations () {
const mq = useMediaQuery()
return (
-
+ Keystone for organisations
diff --git a/docs/app/(site)/for-organisations/page.tsx b/docs/app/(site)/for-organisations/page.tsx
new file mode 100644
index 00000000000..1a34b44c0e2
--- /dev/null
+++ b/docs/app/(site)/for-organisations/page.tsx
@@ -0,0 +1,11 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'KeystoneJS for Organisations',
+ description:
+ 'Discover how Keystone’s flexibility lets organisations scale fast and sustainably with a backend that can be shaped to any business logic.',
+}
+
+export default function ForOrganisations () {
+ return
+}
diff --git a/docs/app/(site)/layout-client.tsx b/docs/app/(site)/layout-client.tsx
new file mode 100644
index 00000000000..39e4c8a0d64
--- /dev/null
+++ b/docs/app/(site)/layout-client.tsx
@@ -0,0 +1,211 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { CacheProvider } from '@emotion/react'
+import { useServerInsertedHTML } from 'next/navigation'
+import { useContext, useEffect, useMemo, useState } from 'react'
+import createCache from '@emotion/cache'
+import Script from 'next/script'
+import { Global, css } from '@emotion/react'
+
+import { proseStyles } from '../../lib/prose-lite'
+import { Theme } from '../../components/Theme'
+import { NavContextProvider } from '../../components/docs/Navigation'
+import { SkipLinks } from '../../components/SkipLinks'
+import { createContext } from 'react'
+
+function getTheme () {
+ const isSystemColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)').matches
+ const localStorageTheme = localStorage.theme
+ if ((!localStorageTheme && isSystemColorSchemeDark) || localStorageTheme === 'dark') {
+ return 'dark'
+ }
+ return 'light'
+}
+
+const ThemeContext = createContext({
+ theme: 'light' as 'dark' | 'light',
+ setTheme: (theme: 'dark' | 'light') => {},
+})
+
+export function useThemeContext () {
+ return useContext(ThemeContext)
+}
+
+export function Html (props: { children: React.ReactNode}) {
+ const [theme, setTheme] = useState<'light' | 'dark'>(() => {
+ if (typeof window === 'undefined') return 'light'
+ return getTheme()
+ })
+ useEffect(() => {
+ const listener = () => {
+ setTheme(getTheme())
+ }
+ const match = window.matchMedia('(prefers-color-scheme: dark)')
+ match.addEventListener('change', listener)
+ return () => {
+ match.removeEventListener('change', listener)
+ }
+ }, [])
+ const context = useMemo(() => ({
+ theme,
+ setTheme (theme:'dark' | 'light') {
+ setTheme((theme) => (theme === 'dark' ? 'light' : 'dark'))
+ localStorage.setItem('theme', theme)
+ }
+ }), [theme, setTheme])
+ return {props.children}
+}
+
+export default function RootLayoutClient ({ children }: { children: React.ReactNode }) {
+ const [{ cache, flush }] = useState(() => {
+ const cache = createCache({ key: 'my' })
+ cache.compat = true
+ const prevInsert = cache.insert
+ let inserted: string[] = []
+ cache.insert = (...args) => {
+ const serialized = args[1]
+ if (cache.inserted[serialized.name] === undefined) {
+ inserted.push(serialized.name)
+ }
+ return prevInsert(...args)
+ }
+ const flush = () => {
+ const prevInserted = inserted
+ inserted = []
+ return prevInserted
+ }
+ return { cache, flush }
+ })
+
+ useServerInsertedHTML(() => {
+ const names = flush()
+ if (names.length === 0) return null
+ let styles = ''
+ for (const name of names) {
+ styles += cache.inserted[name]
+ }
+ return (
+
+ )
+ })
+
+ return (
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+ )
+}
diff --git a/docs/app/(site)/layout.tsx b/docs/app/(site)/layout.tsx
new file mode 100644
index 00000000000..f2cb4e4fd83
--- /dev/null
+++ b/docs/app/(site)/layout.tsx
@@ -0,0 +1,107 @@
+import type { Metadata } from 'next'
+import RootLayoutClient, { Html } from './layout-client'
+import { siteBaseUrl } from '../../lib/og-util'
+
+const defaultTitle = 'KeystoneJS: The superpowered Node.js Headless CMS for developers'
+const defaultDescription =
+ 'Build faster and scale further with the programmable open source GraphQL API back-end for structured content projects.'
+
+export const metadata: Metadata = {
+ metadataBase: new URL(siteBaseUrl),
+ title: defaultTitle,
+ description: defaultDescription,
+ icons: {
+ apple: '/apple-touch-icon.png',
+ icon: [
+ { url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
+ { url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
+ ],
+ shortcut: '/favicon.ico',
+ },
+ openGraph: {
+ title: defaultTitle,
+ siteName: defaultTitle,
+ description: defaultDescription,
+ type: 'website',
+ locale: 'en',
+ images: [
+ {
+ url: '/og-image-landscape.png',
+ width: 761,
+ height: 410,
+ alt: defaultDescription,
+ },
+ ],
+ },
+ twitter: {
+ title: defaultTitle,
+ description: defaultDescription,
+ card: 'summary_large_image',
+ images: [
+ {
+ url: '/og-image-landscape.png',
+ width: 761,
+ height: 410,
+ alt: defaultTitle,
+ },
+ ],
+ },
+ other: {
+ 'msapplication-TileColor': '#2684FF',
+ 'msapplication-config': '/browserconfig.xml',
+ },
+}
+
+export default function RootLayout ({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+ )
+}
diff --git a/docs/pages/404.tsx b/docs/app/(site)/not-found.tsx
similarity index 70%
rename from docs/pages/404.tsx
rename to docs/app/(site)/not-found.tsx
index 151825598b7..addcf907b6e 100644
--- a/docs/pages/404.tsx
+++ b/docs/app/(site)/not-found.tsx
@@ -1,11 +1,12 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-import { useRouter } from 'next/router'
+/** @jsxImportSource @emotion/react */
-import { Highlight } from '../components/primitives/Highlight'
-import { Type } from '../components/primitives/Type'
-import { Page } from '../components/Page'
+'use client'
+
+import { usePathname } from 'next/navigation'
+
+import { Highlight } from '../../components/primitives/Highlight'
+import { Type } from '../../components/primitives/Type'
+import { Page } from '../../components/Page'
function ConstructionIllustration () {
return (
@@ -34,11 +35,16 @@ function ConstructionIllustration () {
// see https://github.com/keystonejs/keystone/pull/6411#issuecomment-906085389
const v5PathList = ['/tutorials', '/guides', '/keystonejs', '/api', '/discussions']
+export const metadata = {
+ title: 'Page Not Found (404)',
+ description: 'The page you are looking for could not be found.',
+}
+
export default function NotFoundPage () {
- const { asPath } = useRouter()
- const tryV5Link = v5PathList.some(x => asPath.startsWith(x))
+ const pathname = usePathname()
+ const tryV5Link = v5PathList.some((x) => pathname.startsWith(x))
return (
-
+
If you were looking for a page in the Keystone 5 docs, try{' '}
- https://v5.keystonejs.com{asPath}.
+ https://v5.keystonejs.com{pathname}
+ .
) : null}
diff --git a/docs/pages/index.tsx b/docs/app/(site)/page-client.tsx
similarity index 91%
rename from docs/pages/index.tsx
rename to docs/app/(site)/page-client.tsx
index e8b725978d7..aa60bf23e1c 100644
--- a/docs/pages/index.tsx
+++ b/docs/app/(site)/page-client.tsx
@@ -1,55 +1,51 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import Image from 'next/image'
import Link from 'next/link'
-import { useMediaQuery } from '../lib/media'
-import { CodeWindow, WindowWrapper, WindowL, WindowR } from '../components/content/CodeWindow'
-import { IntroWrapper, IntroHeading, IntroLead } from '../components/content/Intro'
-import { CommunityCta } from '../components/content/CommunityCta'
-import { FrontEndLogos } from '../components/icons/FrontEndLogos'
-import { Highlight } from '../components/primitives/Highlight'
-import { WhyKeystone } from '../components/icons/WhyKeystone'
-import { MWrapper } from '../components/content/MWrapper'
-import { Relational } from '../components/icons/Relational'
-import { TweetBox } from '../components/content/TweetBox'
-import { Typescript } from '../components/icons/Typescript'
-import { Code as SourceCode, InlineCode } from '../components/primitives/Code'
-import { Automated } from '../components/icons/Automated'
-import { Section } from '../components/content/Section'
-import { Thinkmill } from '../components/icons/Thinkmill'
-import { CodeBox } from '../components/content/CodeBox'
-import { PillCta } from '../components/content/PillCta'
-import { Migration } from '../components/icons/Migration'
-import { Button } from '../components/primitives/Button'
-import { EndCta } from '../components/content/EndCta'
-import { Emoji } from '../components/primitives/Emoji'
-import { Updates } from '../components/icons/Updates'
-import { Type } from '../components/primitives/Type'
-import { ArrowR } from '../components/icons/ArrowR'
-import { Custom } from '../components/icons/Custom'
-import { Filter } from '../components/icons/Filter'
-import { Shield } from '../components/icons/Shield'
-import { Watch } from '../components/icons/Watch'
-import { Tick } from '../components/icons/Tick'
-import { Page } from '../components/Page'
-import { Organization } from '../components/icons/Organization'
-import { Content } from '../components/icons/Content'
-import { Code } from '../components/icons/Code'
-import contentEditorMockui from '../public/assets/content-editor-mockui.png'
-import docEditorHome from '../public/assets/doc-editor-home.png'
-import deployTargets from '../public/assets/deploy-targets.png'
+import { useMediaQuery } from '../../lib/media'
+import { CodeWindow, WindowWrapper, WindowL, WindowR } from '../../components/content/CodeWindow'
+import { IntroWrapper, IntroHeading, IntroLead } from '../../components/content/Intro'
+import { CommunityCta } from '../../components/content/CommunityCta'
+import { FrontEndLogos } from '../../components/icons/FrontEndLogos'
+import { Highlight } from '../../components/primitives/Highlight'
+import { WhyKeystone } from '../../components/icons/WhyKeystone'
+import { MWrapper } from '../../components/content/MWrapper'
+import { Relational } from '../../components/icons/Relational'
+import { TweetBox } from '../../components/content/TweetBox'
+import { Typescript } from '../../components/icons/Typescript'
+import { Code as SourceCode, InlineCode } from '../../components/primitives/Code'
+import { Automated } from '../../components/icons/Automated'
+import { Section } from '../../components/content/Section'
+import { Thinkmill } from '../../components/icons/Thinkmill'
+import { CodeBox } from '../../components/content/CodeBox'
+import { PillCta } from '../../components/content/PillCta'
+import { Migration } from '../../components/icons/Migration'
+import { Button } from '../../components/primitives/Button'
+import { EndCta } from '../../components/content/EndCta'
+import { Emoji } from '../../components/primitives/Emoji'
+import { Updates } from '../../components/icons/Updates'
+import { Type } from '../../components/primitives/Type'
+import { ArrowR } from '../../components/icons/ArrowR'
+import { Custom } from '../../components/icons/Custom'
+import { Filter } from '../../components/icons/Filter'
+import { Shield } from '../../components/icons/Shield'
+import { Watch } from '../../components/icons/Watch'
+import { Tick } from '../../components/icons/Tick'
+import { Page } from '../../components/Page'
+import { Organization } from '../../components/icons/Organization'
+import { Content } from '../../components/icons/Content'
+import { Code } from '../../components/icons/Code'
+import contentEditorMockui from '../../public/assets/content-editor-mockui.png'
+import docEditorHome from '../../public/assets/doc-editor-home.png'
+import deployTargets from '../../public/assets/deploy-targets.png'
-export default function IndexPage () {
+export default function PageClient () {
const mq = useMediaQuery()
return (
-
+
@@ -57,12 +53,12 @@ export default function IndexPage () {
Keystone helps you build faster and scale further than any other CMS or App Framework.
- Just describe your schema, and get a powerful GraphQL API & beautiful Management UI for
+ Describe your schema, and you get a powerful GraphQL API & beautiful Management UI for
content and data.
- No boilerplate or bootstrapping – just elegant APIs to help you ship the code that
- matters without sacrificing the flexibility or power of a bespoke back-end.
+ No boilerplate or bootstrapping – only elegant APIs to help you ship the code that
+ matters, without sacrificing the flexibility or power of a bespoke back-end.
@@ -75,7 +71,7 @@ export default function IndexPage () {
marginTop: '2.5rem',
})}
>
-
+