diff --git a/next-canary/package.json b/next-canary/package.json
index 97312a4..82bee80 100644
--- a/next-canary/package.json
+++ b/next-canary/package.json
@@ -5,6 +5,7 @@
"scripts": {
"build": "next build",
"dev": "next dev --turbopack",
+ "typegen:generate": "sanity typegen generate",
"lint": "next lint",
"start": "next start"
},
diff --git a/next-canary/sanity-typegen.json b/next-canary/sanity-typegen.json
new file mode 100644
index 0000000..9cc38d4
--- /dev/null
+++ b/next-canary/sanity-typegen.json
@@ -0,0 +1,3 @@
+{
+ "schema": "../studio/schema.json"
+}
diff --git a/next-canary/src/app/SanityLive.tsx b/next-canary/src/app/SanityLive.tsx
new file mode 100644
index 0000000..f100a20
--- /dev/null
+++ b/next-canary/src/app/SanityLive.tsx
@@ -0,0 +1,48 @@
+'use client'
+
+import {client} from '@/sanity/client'
+import type {LiveEventMessage, LiveEventRestart, LiveEventWelcome} from '@sanity/client'
+import {CorsOriginError} from '@sanity/client'
+import {useRouter} from 'next/navigation'
+import {useEffect} from 'react'
+import {useEffectEvent} from 'use-effect-event'
+import {expireTags} from './actions'
+
+export function SanityLive() {
+ const router = useRouter()
+
+ const handleLiveEvent = useEffectEvent(
+ (event: LiveEventMessage | LiveEventRestart | LiveEventWelcome) => {
+ if (event.type === 'welcome') {
+ console.info('Sanity is live with automatic invalidation of published content')
+ } else if (event.type === 'message') {
+ expireTags(event.tags).then(() => router.refresh())
+ } else if (event.type === 'restart') {
+ router.refresh()
+ }
+ },
+ )
+ useEffect(() => {
+ const subscription = client.live.events().subscribe({
+ next: (event) => {
+ if (event.type === 'message' || event.type === 'restart' || event.type === 'welcome') {
+ handleLiveEvent(event)
+ }
+ },
+ error: (error: unknown) => {
+ if (error instanceof CorsOriginError) {
+ console.warn(
+ `Sanity Live is unable to connect to the Sanity API as the current origin - ${window.origin} - is not in the list of allowed CORS origins for this Sanity Project.`,
+ error.addOriginUrl && `Add it here:`,
+ error.addOriginUrl?.toString(),
+ )
+ } else {
+ console.error(error)
+ }
+ },
+ })
+ return () => subscription.unsubscribe()
+ }, [])
+
+ return null
+}
diff --git a/next-canary/src/app/ThemeButton.tsx b/next-canary/src/app/ThemeButton.tsx
new file mode 100644
index 0000000..74b37f3
--- /dev/null
+++ b/next-canary/src/app/ThemeButton.tsx
@@ -0,0 +1,22 @@
+'use client'
+
+import type {SyncTag} from '@sanity/client'
+import {useTransition} from 'react'
+import {randomColorTheme} from './actions'
+
+export function ThemeButton({tags}: {tags: SyncTag[]}) {
+ const [pending, startTransition] = useTransition()
+ return (
+
+ )
+}
diff --git a/next-canary/src/app/actions.ts b/next-canary/src/app/actions.ts
new file mode 100644
index 0000000..5718f57
--- /dev/null
+++ b/next-canary/src/app/actions.ts
@@ -0,0 +1,17 @@
+'use server'
+
+import type {SyncTag} from '@sanity/client'
+import {expireTag} from 'next/cache'
+
+export async function expireTags(tags: SyncTag[]) {
+ expireTag(...tags)
+ console.log(` expired tags: ${tags.join(', ')}`)
+}
+
+export async function randomColorTheme(tags: SyncTag[]) {
+ const res = await fetch('https://lcapi-examples-api.sanity.dev/api/random-color-theme', {
+ method: 'PUT',
+ })
+ expireTag(...tags)
+ return res.json()
+}
diff --git a/next-canary/src/app/layout.tsx b/next-canary/src/app/layout.tsx
index f29595d..52c6f15 100644
--- a/next-canary/src/app/layout.tsx
+++ b/next-canary/src/app/layout.tsx
@@ -1,20 +1,41 @@
-import type {Metadata} from 'next'
+import type {Viewport} from 'next'
import './globals.css'
+import {sanityFetch} from '@/sanity/fetch'
+import {defineQuery} from 'groq'
+import {SanityLive} from './SanityLive'
+import {ThemeButton} from './ThemeButton'
-export const metadata: Metadata = {
- title: 'Create Next App',
- description: 'Generated by create next app',
+const THEME_QUERY = defineQuery(`*[_id == "theme"][0]{background,text}`)
+
+export async function generateViewport(): Promise {
+ const {data} = await sanityFetch({query: THEME_QUERY})
+ return {
+ themeColor: data?.background,
+ }
}
-export default function RootLayout({
+export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
+ const {data, tags} = await sanityFetch({query: THEME_QUERY})
+
return (
-
-
- {children}
+
+
+
+ {children}
+
+
+
)
diff --git a/next-canary/src/app/page.tsx b/next-canary/src/app/page.tsx
index b34149c..6210a69 100644
--- a/next-canary/src/app/page.tsx
+++ b/next-canary/src/app/page.tsx
@@ -1,83 +1,24 @@
-import Image from 'next/image'
+import {sanityFetch} from '@/sanity/fetch'
+import './globals.css'
+import {defineQuery} from 'groq'
+import type {Metadata} from 'next'
-export default function Home() {
- return (
-
-
-
-
- -
- Get started by editing{' '}
-
- src/app/page.tsx
-
- .
-
- - Save and see your changes instantly.
-
+const DEMO_QUERY = defineQuery(`*[_type == "demo" && slug.current == $slug][0].title`)
+const slug = 'next-canary'
+
+export async function generateMetadata(): Promise {
+ const {data} = await sanityFetch({query: DEMO_QUERY, params: {slug}})
+ return {
+ title: data || 'Next Canary',
+ }
+}
-
-
-
-
+export default async function Home() {
+ const {data} = await sanityFetch({query: DEMO_QUERY, params: {slug}})
+
+ return (
+
+ {data || 'Next Canary'}
+
)
}
diff --git a/next-canary/src/sanity/client.ts b/next-canary/src/sanity/client.ts
new file mode 100644
index 0000000..6712b86
--- /dev/null
+++ b/next-canary/src/sanity/client.ts
@@ -0,0 +1,8 @@
+import {createClient} from '@sanity/client'
+
+export const client = createClient({
+ projectId: 'hiomol4a',
+ dataset: 'lcapi',
+ apiVersion: '2024-09-18',
+ useCdn: false,
+})
diff --git a/next-canary/src/sanity/fetch.ts b/next-canary/src/sanity/fetch.ts
new file mode 100644
index 0000000..fc20386
--- /dev/null
+++ b/next-canary/src/sanity/fetch.ts
@@ -0,0 +1,19 @@
+import {type QueryParams} from '@sanity/client'
+import {unstable_cacheTag as cacheTag} from 'next/cache'
+import {client} from './client'
+
+export async function sanityFetch({
+ query,
+ params = {},
+}: {
+ query: QueryString
+ params?: QueryParams
+}) {
+ 'use cache'
+ const {result, syncTags} = await client.fetch(query, params, {
+ filterResponse: false,
+ })
+ cacheTag(...(syncTags as string[]))
+
+ return {data: result, tags: syncTags}
+}