Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for Next 13 appDir #184

Merged
merged 1 commit into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
- run: npm ci
- run: npm run lint
if: github.event.inputs.test != 'false'
# @TODO find out how to make this command work on the CI
#- run: npm run update:icons
# if: github.event.inputs.test != 'false'
- run: npm run prepublishOnly
- run: npm run test:node-esm-cjs
name: Test if Node.js ESM native modes works, without breaking CJS modes
Expand Down
254 changes: 149 additions & 105 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,17 @@
- [Limits](#limits)
- [`next-sanity/studio` (dev-preview)](#next-sanitystudio-dev-preview)
- [Usage](#usage)
- [Next 13 `app/studio`](#next-13-appstudio)
- [Next 12 or `pages/studio`](#next-12-or-pagesstudio)
- [Opt-in to using `StudioProvider` and `StudioLayout`](#opt-in-to-using-studioprovider-and-studiolayout)
- [Customize `<ServerStyleSheetDocument />`](#customize-serverstylesheetdocument-)
- [Full-control mode](#full-control-mode)
- [`next-sanity/webhook`](#next-sanitywebhook)
- [Migrate](#migrate)
- [From `v2`](#from-v2)
- [`NextStudioGlobalStyle` is removed](#nextstudioglobalstyle-is-removed)
- [`ServerStyleSheetDocument` is removed](#serverstylesheetdocument-is-removed)
- [The internal `isWorkspaceWithTheme` and `isWorkspaces` utils are no longer exported](#the-internal-isworkspacewiththeme-and-isworkspaces-utils-are-no-longer-exported)
- [The `useBackgroundColorsFromTheme`, `useBasePath`, `useConfigWithBasePath`, and `useTextFontFamilyFromTheme`, hooks are removed](#the-usebackgroundcolorsfromtheme-usebasepath-useconfigwithbasepath-and-usetextfontfamilyfromtheme-hooks-are-removed)
- [The `NextStudioHead` component has moved from `next-sanity/studio` to `next-sanity/studio/head`](#the-nextstudiohead-component-has-moved-from-next-sanitystudio-to-next-sanitystudiohead)
- [From `v1`](#from-v1)
- [`createPreviewSubscriptionHook` is replaced with `definePreview`](#createpreviewsubscriptionhook-is-replaced-with-definepreview)
- [Before](#before)
Expand Down Expand Up @@ -450,149 +456,142 @@ The latest version of Sanity Studio allows you to embed a near-infinitely config

### Usage

The basic setup is two files:
The basic setup is 2 components, `NextStudio` and `NextStudioHead`.
`NextStudio` loads up the `import {Studio} from 'sanity'` component for you and wraps it in a Next-friendly layout.
While `NextStudioHead` sets necessary `<head>` meta tags such as `<meta name="viewport">` to ensure the responsive CSS in the Studio works as expected.

1. `pages/[[...index]].tsx`
Both the Next 13 and 12 examples uses this config file:
`sanity.config.ts`:

```tsx
// Import your sanity.config.ts file
import config from '../sanity.config'
import {NextStudio} from 'next-sanity/studio'
```ts
import {visionTool} from '@sanity/vision'
import {defineConfig} from 'sanity'
import {deskTool} from 'sanity/desk'

export default function StudioPage() {
// Loads the Studio, with all the needed meta tags and global CSS required for it to render correctly
return <NextStudio config={config} />
}
```
import {schemaTypes} from './schemas'

The `<NextStudio />` wraps `<Studio />` component and supports forwarding all its props:
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET!

```tsx
import {Studio} from 'sanity'
export default defineConfig({
basePath: '/studio', // <-- important that `basePath` matches the route you're mounting your studio from, it applies to both `/pages` and `/app`

projectId,
dataset,

plugins: [deskTool(), visionTool()],

schema: {
types: schemaTypes,
},
})
```

2. `pages/_document.tsx`
To use `sanity.cli.ts` with the same `projectId` and `dataset` as your `sanity.config.ts`:

```tsx
import {ServerStyleSheetDocument} from 'next-sanity/studio'
```ts
/* eslint-disable no-process-env */
import {loadEnvConfig} from '@next/env'
import {defineCliConfig} from 'sanity/cli'

const dev = process.env.NODE_ENV !== 'production'
loadEnvConfig(__dirname, dev, {info: () => null, error: console.error})

// Set up SSR for styled-components, ensuring there's no missing CSS when deploying a Studio in Next.js into production
export default class Document extends ServerStyleSheetDocument {}
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET

export default defineCliConfig({api: {projectId, dataset}})
```

### Opt-in to using `StudioProvider` and `StudioLayout`
Now you can run commands like `npx sanity cors add`. See `npx sanity help` for a full list of what you can do.

If you want to go lower level and have more control over the studio you can pass `StudioProvider` and `StudioLayout` from `sanity` as `children`:
#### Next 13 `app/studio`

In Next 13's `appDir` mode you use `page.tsx` to load `NextStudio`, and optionally (recommended, especially if you want great support for iPhones and other devices with display cutouts like "The Notch" or "Dynamic Island") export `NextStudioHead` in a `head.tsx`.
In routes that load `NextStudio` ensure you have `'use client'` at the top of your file.

`app/studio/[[...index]]/page.tsx`:

```tsx
'use client'

import {NextStudio} from 'next-sanity/studio'
import {StudioProvider, StudioLayout} from 'sanity'

import config from '../sanity.config'
import config from '../../../sanity.config'

function StudioPage() {
return (
<NextStudio config={config}>
<StudioProvider config={config}>
{/* Put components here and you'll have access to the same React hooks as Studio gives you when writing plugins */}
<StudioLayout />
</StudioProvider>
</NextStudio>
)
export default function StudioPage() {
// Supports the same props as `import {Studio} from 'sanity'`, `config` is required
return <NextStudio config={config} />
}
```

### Customize `<ServerStyleSheetDocument />`

You can still customize `_document.tsx`, the same way you would the default `<Document />` component from `next/document`:
`app/studio/[[...index]]/head.tsx`:

```tsx
import {ServerStyleSheetDocument} from 'next-sanity/studio'

export default class Document extends ServerStyleSheetDocument {
static async getInitialProps(ctx: DocumentContext) {
// You can still override renderPage:
const originalRenderPage = ctx.renderPage
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => <App {...props} />,
})

const initialProps = await ServerStyleSheetDocument.getInitialProps(ctx)

const extraStyles = await getStyles()
return {
...initialProps,
// Add to the default styles if you want
styles: [initialProps.styles, extraStyles],
}
}
render() {
// do the same stuff as in `next/document`
}
// Re-export `NextStudioHead` as default if you're happy with the default behavior
export {NextStudioHead as default} from 'next-sanity/studio/head'

// To customize it, use it as a children component:
import {NextStudioHead} from 'next-sanity/studio/head'

export default function CustomStudioHead() {
return (
<>
<NextStudioHead favicons={false} />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="https://www.sanity.io/static/images/favicons/favicon-32x32.png"
/>
</>
)
}
```

### Full-control mode
#### Next 12 or `pages/studio`

Using just `NextStudio` gives you a fully working Sanity Studio v3. However we recommend also using `NextStudioHead` as it ensures CSS Media Queries that target mobile devices with display cutouts (for example iPhone's "The Notch" and "Dynamic Island") and other details.

If you only need parts of what `<NextStudio />` does for you, but not all of it.
No problem. You can import any which one of the components that `<NextStudio />` is importing and assemble them in any way you want.
`/pages/studio/[[...index]].tsx`:

```tsx
import {Studio, type Config} from 'sanity'
import {NextStudioGlobalStyle, NextStudioHead} from 'next-sanity/studio'
// This implementation will only load the bare minimum of what's required for the Studio to render correctly. No favicons, fancy <meta name="theme-color"> tags or the like
export default function CustomNextStudio({config}: {config: Config}) {
import Head from 'next/head'
import {NextStudio} from 'next-sanity/studio'
import {NextStudioHead} from 'next-sanity/studio/head'

import config from '../../sanity.config'

export default function StudioPage() {
return (
<>
<Studio config={config} />
<NextStudioHead>{/* Custom extra stuff in <head> */}</NextStudioHead>
<NextStudioGlobalStyle />
<Head>
<NextStudioHead />
</Head>
<NextStudio config={config} />
</>
)
}
```

And while `<NextStudio />` have all features enabled by default allowing you to opt-out by giving it props, the inner components `<NextStudioHead />` and `<NextStudioGlobalStyle />` are opt-in.
This means that these two `StudioPage` components are functionally identical:
### Opt-in to using `StudioProvider` and `StudioLayout`

If you want to go lower level and have more control over the studio you can pass `StudioProvider` and `StudioLayout` from `sanity` as `children`:

```tsx
import {
NextStudio,
NextStudioGlobalStyle,
NextStudioHead,
useTheme,
useBackgroundColorsFromTheme,
} from 'next-sanity/studio'
import {Studio} from 'sanity'
import config from '../sanity.config'

// Turning all the features off, leaving only bare minimum required meta tags and styling
function StudioPage() {
return (
<NextStudio
config={config}
// an empty string turns off the CSS that sets a background on <html>
unstable__bg=""
unstable__noTailwindSvgFix
unstable__noFavicons
// an empty string turns off the <title> tag
unstable__document_title=""
/>
)
}
import {NextStudio} from 'next-sanity/studio'
import {StudioProvider, StudioLayout} from 'sanity'

// Since no features are enabled it works the same way
function Studiopage() {
const theme = useTheme(config)
const {themeColorLight, themeColorDark} = useBackgroundColorsFromTheme(theme)
import config from '../../../sanity.config'

function StudioPage() {
return (
<>
<Studio config={config} />
<NextStudioHead themeColorLight={themeColorLight} themeColorDark={themeColorDark} />
<NextStudioGlobalStyle />
</>
<NextStudio config={config}>
<StudioProvider config={config}>
{/* Put components here and you'll have access to the same React hooks as Studio gives you when writing plugins */}
<StudioLayout />
</StudioProvider>
</NextStudio>
)
}
```
Expand Down Expand Up @@ -635,6 +634,51 @@ export default async function revalidate(req: NextApiRequest, res: NextApiRespon

## Migrate

### From `v2`

The `v3` release only contains breaking changes on the `next-sanity/studio` imports. If you're only using `import {createClient, groq} from 'next-sanity'` or `import {definePreview, PreviewSuspense} from 'next-sanity/preview'` then there's no migration for you to do.

#### `NextStudioGlobalStyle` is removed

The layout is no longer using global CSS to set the Studio height. The switch to local CSS helps interop between Next 12 and 13 layouts.

#### `ServerStyleSheetDocument` is removed

It's no longer necessary to setup `styled-components` SSR for the Studio to render correctly.

#### The internal `isWorkspaceWithTheme` and `isWorkspaces` utils are no longer exported

The `useTheme` hook is still available if you're building abstractions that need to know what the initial workspace theme variables are.

#### The `useBackgroundColorsFromTheme`, `useBasePath`, `useConfigWithBasePath`, and `useTextFontFamilyFromTheme`, hooks are removed

You can `useTheme` to replace `useBackgroundColorsFromTheme` and `useTextFontFamilyFromTheme`:

```tsx
import {useMemo} from 'react'
import {useTheme} from 'next-sanity/studio'
import type {StudioProps} from 'sanity'
export default function MyComponent(props: Pick<StudioProps, 'config'>) {
const theme = useTheme(config)
// useBackgroundColorsFromTheme
const {themeColorLight, themeColorDark} = useMemo(
() => ({
themeColorLight: theme.color.light.default.base.bg,
themeColorDark: theme.color.dark.default.base.bg,
}),
[theme]
)
// useTextFontFamilyFromTheme
const fontFamily = useMemo(() => theme.fonts.text.family, [theme])
}
```

The reason why `useBasePath` and `useConfigWithBasePath` got removed is because Next 12 and 13 diverge too much in how they declare dynamic segments. Thus you'll need to specify `basePath` in your `sanity.config.ts` manually to match the route you're loading the studio, for the time being.

#### The `NextStudioHead` component has moved from `next-sanity/studio` to `next-sanity/studio/head`

Its props are also quite different and it now requires you to wrap it in `import Head from 'next/head'` if you're not using a `head.tsx` in `appDir`. Make sure you use TypeScript to ease the migration.

### From `v1`

#### `createPreviewSubscriptionHook` is replaced with `definePreview`
Expand Down
5 changes: 3 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PreviewPosts from 'app/PreviewPosts'
import {createClient} from 'app/sanity.client'
import {PreviewSuspense} from 'app/sanity.preview'
import {previewData} from 'next/headers'
import Link from 'next/link'

export default async function IndexPage() {
const thePreviewData = previewData()
Expand Down Expand Up @@ -66,12 +67,12 @@ export default async function IndexPage() {
</div>
</div>
<div className="text-center">
<a
<Link
href="/studio"
className="mx-2 my-4 inline-block rounded-full border border-gray-200 px-4 py-1 text-sm font-semibold text-gray-600 hover:border-transparent hover:bg-gray-600 hover:text-white focus:outline-none focus:ring-2 focus:ring-gray-600 focus:ring-offset-2"
>
Open Studio
</a>
</Link>
</div>
</>
)
Expand Down
1 change: 1 addition & 0 deletions app/studio/[[...tool]]/head.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {NextStudioHead as default} from 'src/studio/head'
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
'use client'

import config from 'sanity.config'
import {NextStudio, useConfigWithBasePath} from 'src/studio'
import {NextStudio} from 'src/studio'

export default function StudioPage() {
return (
<NextStudio
config={useConfigWithBasePath(config)}
config={config}
// Turn off login in production so that anyone can look around in the Studio and see how it works
// eslint-disable-next-line no-process-env
unstable_noAuthBoundary={process.env.NEXT_PUBLIC_VERCEL_ENV === 'production'}
Expand Down
14 changes: 14 additions & 0 deletions favicons.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
declare module '*.ico' {
const value: string
export default value
}

declare module '*.svg' {
const value: string
export default value
}

declare module '*.png' {
const value: string
export default value
}
1 change: 0 additions & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.