├── .changeset ├── README.md └── config.json ├── .github ├── CODEOWNERS ├── renovate.json5 └── workflows │ ├── changesets-renovate.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierrc.mjs ├── README.md ├── apps ├── next │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── app │ │ ├── (website) │ │ │ ├── [...path] │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── api │ │ │ ├── disable-draft │ │ │ │ └── route.ts │ │ │ └── draft │ │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── icon.png │ │ └── layout.tsx │ ├── components │ │ ├── Page.tsx │ │ └── SanityImage.tsx │ ├── config.ts │ ├── data │ │ └── sanity │ │ │ ├── client.ts │ │ │ ├── generateStaticSlugs.ts │ │ │ ├── index.ts │ │ │ ├── loadQuery.ts │ │ │ └── queries.ts │ ├── next-env.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ ├── logo.png │ │ └── studio.png │ ├── styles │ │ └── index.css │ ├── tailwind.config.js │ ├── tsconfig.json │ └── types │ │ └── index.ts └── studio │ ├── .eslintrc │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── sanity.cli.ts │ ├── sanity.config.ts │ ├── schemas │ ├── author.ts │ ├── index.ts │ ├── page.ts │ └── post.ts │ ├── static │ └── .gitkeep │ └── tsconfig.json ├── examples ├── hello-world │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── app │ │ ├── (website) │ │ │ ├── [...path] │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── api │ │ │ ├── disable-draft │ │ │ │ └── route.ts │ │ │ └── draft │ │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── icon.png │ │ ├── layout.tsx │ │ └── studio │ │ │ └── [[...index]] │ │ │ ├── Studio.tsx │ │ │ └── page.tsx │ ├── components │ │ ├── Page.tsx │ │ └── StudioLogo.tsx │ ├── config.ts │ ├── data │ │ └── sanity │ │ │ ├── client.ts │ │ │ ├── generateStaticSlugs.ts │ │ │ ├── index.ts │ │ │ ├── loadQuery.ts │ │ │ └── queries.ts │ ├── next-env.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ ├── logo.png │ │ └── studio.png │ ├── sanity.cli.ts │ ├── sanity.config.ts │ ├── sanity │ │ └── schemas │ │ │ ├── index.ts │ │ │ └── page.tsx │ ├── styles │ │ └── index.css │ ├── tailwind.config.js │ ├── tsconfig.json │ └── types │ │ └── index.ts ├── with-i18n │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── app │ │ ├── (website) │ │ │ └── [locale] │ │ │ │ ├── [...path] │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ ├── api │ │ │ ├── disable-draft │ │ │ │ └── route.ts │ │ │ └── draft │ │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── icon.png │ │ └── studio │ │ │ ├── [[...index]] │ │ │ ├── Studio.tsx │ │ │ └── page.tsx │ │ │ └── layout.tsx │ ├── components │ │ ├── Page.tsx │ │ └── StudioLogo.tsx │ ├── config.ts │ ├── data │ │ └── sanity │ │ │ ├── client.ts │ │ │ ├── generateStaticSlugs.ts │ │ │ ├── index.ts │ │ │ ├── loadQuery.ts │ │ │ └── queries.ts │ ├── middleware.ts │ ├── next-env.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── static │ │ │ ├── logo.png │ │ │ └── studio.png │ ├── sanity.cli.ts │ ├── sanity.config.ts │ ├── sanity │ │ └── schemas │ │ │ ├── index.ts │ │ │ └── page.ts │ ├── styles │ │ └── index.css │ ├── tailwind.config.js │ ├── tsconfig.json │ └── types │ │ └── index.ts └── with-sections │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── app │ ├── (website) │ │ ├── [...path] │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ ├── api │ │ ├── disable-draft │ │ │ └── route.ts │ │ └── draft │ │ │ └── route.ts │ ├── favicon.ico │ ├── icon.png │ ├── layout.tsx │ └── studio │ │ └── [[...index]] │ │ ├── Studio.tsx │ │ └── page.tsx │ ├── components │ ├── Page.tsx │ ├── StudioLogo.tsx │ └── sections │ │ ├── Header.tsx │ │ ├── Hero.tsx │ │ ├── Logos.tsx │ │ ├── Testimonials.tsx │ │ └── index.tsx │ ├── config.ts │ ├── data │ └── sanity │ │ ├── client.ts │ │ ├── generateStaticSlugs.ts │ │ ├── index.ts │ │ ├── loadQuery.ts │ │ └── queries.ts │ ├── next-env.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── logo.png │ ├── sections │ │ ├── section.header.png │ │ ├── section.hero.png │ │ ├── section.logos.png │ │ └── section.testimonials.png │ └── studio.png │ ├── sanity.cli.ts │ ├── sanity.config.ts │ ├── sanity │ └── schemas │ │ ├── index.ts │ │ ├── page.ts │ │ └── sections │ │ ├── header.ts │ │ ├── hero.ts │ │ ├── index.ts │ │ ├── logos.ts │ │ └── testimonials.ts │ ├── styles │ └── index.css │ ├── tailwind.config.js │ ├── tsconfig.json │ └── types │ └── index.ts ├── package.json ├── packages ├── eslint-config │ ├── README.md │ ├── library.js │ ├── next.js │ ├── package.json │ └── react-internal.js ├── sanity-studio │ ├── .eslintignore │ ├── .eslintrc │ ├── .gitignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── IconSelectComponent.tsx │ │ │ ├── PathnameFieldComponent.tsx │ │ │ ├── index.ts │ │ │ └── input-with-characters-count.tsx │ │ ├── hooks │ │ │ ├── useAsync.ts │ │ │ ├── usePathnameContext.ts │ │ │ └── usePathnamePrefix.ts │ │ ├── index.ts │ │ ├── plugins │ │ │ ├── disableCreation │ │ │ │ ├── actions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── templates.ts │ │ │ │ └── types.ts │ │ │ ├── i18n │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ └── navigator │ │ │ │ ├── components │ │ │ │ ├── DefaultPagesNavigator.tsx │ │ │ │ ├── Header.tsx │ │ │ │ ├── List.tsx │ │ │ │ ├── LocaleSelect.tsx │ │ │ │ ├── Navigator.tsx │ │ │ │ ├── Preview.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ ├── ThemeProvider.tsx │ │ │ │ └── ToolTipWrapper.tsx │ │ │ │ ├── context │ │ │ │ └── index.tsx │ │ │ │ ├── index.ts │ │ │ │ └── utils │ │ │ │ └── index.tsx │ │ ├── schemas │ │ │ ├── icon.ts │ │ │ └── index.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── definePathname.ts │ │ │ ├── index.ts │ │ │ └── localizedItem.ts │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── sanity-web │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── SanityImage.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── index.ts │ │ │ ├── portable-text │ │ │ └── index.ts │ │ │ ├── strings │ │ │ └── index.ts │ │ │ └── urls │ │ │ └── index.ts │ └── tsconfig.json └── typescript-config │ ├── base.json │ └── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * stilyan-tinloof @siffogh 2 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: "https://docs.renovatebot.com/renovate-schema.json", 3 | extends: [ 4 | "config:recommended", 5 | "schedule:weekly", 6 | "group:allNonMajor", 7 | ":disablePeerDependencies", 8 | ], 9 | timezone: "America/Los_Angeles", 10 | schedule: ["before 4am on monday"], 11 | labels: ["dependencies"], 12 | rangeStrategy: "bump", 13 | includePaths: ["packages/**", "apps/**", "examples/**"], 14 | ignorePaths: ["packages/typescript-config", "packages/eslint-config"], 15 | ignoreDeps: [ 16 | // manually bumping deps 17 | "eslint", 18 | "@sanity/pkg-utils", 19 | "@sanity/plugin-kit", 20 | 21 | // manually bumping workflow actions 22 | "actions/labeler", 23 | 24 | // ignore "engines" update 25 | "node", 26 | "npm", 27 | "pnpm", 28 | ], 29 | packageRules: [ 30 | { 31 | matchPaths: ["apps/**", "examples/**"], 32 | groupName: "apps and examples dependencies", 33 | enabled: true, 34 | }, 35 | { 36 | matchPaths: ["packages/**"], 37 | groupName: "packages dependencies", 38 | enabled: true, 39 | }, 40 | ], 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/changesets-renovate.yml: -------------------------------------------------------------------------------- 1 | name: Generate changeset for Renovate 2 | 3 | on: 4 | pull_request_target: 5 | paths: 6 | - ".github/workflows/changesets-renovate.yml" 7 | - "packages/**/pnpm-lock.yaml" 8 | - "packages/**/package.json" 9 | 10 | jobs: 11 | generate-changeset: 12 | runs-on: ubuntu-latest 13 | if: github.actor == 'renovate[bot]' 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 2 19 | ref: ${{ github.head_ref }} 20 | - name: Git Identity 21 | run: | 22 | git config --global user.name 'tinloof-bot' 23 | git config --global user.email 'bot@tinloof.com' 24 | - uses: pnpm/action-setup@v3 25 | - name: Run changesets-renovate 26 | run: pnpm dlx @scaleway/changesets-renovate@latest 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | id-token: write 17 | pull-requests: write 18 | steps: 19 | - name: Checkout Repo 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup PNPM 23 | uses: pnpm/action-setup@v3 24 | 25 | - name: Setup Node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: 20 29 | cache: "pnpm" 30 | 31 | - name: Install dependencies 32 | run: pnpm run build-install 33 | 34 | - name: Build Packages 35 | run: pnpm run build 36 | 37 | - name: Create Release Pull Request or Publish to npm 38 | id: changesets 39 | uses: changesets/action@v1 40 | with: 41 | # Note: pnpm install after versioning is necessary to refresh lockfile 42 | version: pnpm run version 43 | publish: pnpm exec changeset publish 44 | commit: "[ci] release" 45 | title: "[ci] release" 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 49 | 50 | - uses: actions/github-script@v7 51 | if: steps.changesets.outputs.published == 'true' 52 | name: Parse Changeset `publishedPackages` output 53 | id: parse_packages 54 | with: 55 | script: | 56 | const changes = ${{steps.changesets.outputs.publishedPackages}}; 57 | let message = ":rocket: *New release* :rocket:\n\n\nA new release of *${{github.repository}}* was published!"; 58 | if (Array.isArray(changes) && changes.length > 0) { 59 | message += "\n\n"; 60 | changes.forEach(package => { 61 | message += `*Package:* \`${package.name}\`\n*Version:* \`${package.version}\`\n\n`; 62 | }); 63 | } 64 | message += "Changelog :point_right: https://github.com/${{github.repository}}/releases"; 65 | core.setOutput("blocks", 66 | JSON.stringify({ 67 | "text": message 68 | }) 69 | ); 70 | 71 | - name: Send a Slack notification if a publish happens 72 | if: steps.changesets.outputs.published == 'true' 73 | id: slack 74 | uses: slackapi/slack-github-action@v1.25.0 75 | with: 76 | # This data can be any valid JSON from a previous step in the GitHub Action 77 | payload: ${{steps.parse_packages.outputs.blocks}} 78 | env: 79 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | 40 | # Temp 41 | ___temp 42 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/.npmrc -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | tabWidth: 2, 3 | semi: true, 4 | singleQuote: false, 5 | arrowParens: "always", 6 | bracketSpacing: false, 7 | trailingComma: "all", 8 | }; 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sanity Kit 2 | 3 | Packages to help developing powerful content management experiences with Sanity. 4 | 5 | ## How to contribute 6 | 7 | 1. Make sure to install dependencies from the root folder, this will assure all deps are installed for sub-projects 8 | 9 | 2. Start turbo dev server for all packages by running 10 | 11 | ``` 12 | npm run dev 13 | ``` 14 | 15 | from the root folder 16 | 17 | 3. To preview changes we have `/apps` 18 | 19 | a. `/apps/next` is used to preview `sanity-web` changes 20 | 21 | b. `apps/studio` is used to preview `sanity-studio` changes 22 | 23 | depending on which package you are working on you can spawn the respective app, each app needs `.env` file to link to a sanity project. 24 | 25 | after updating the `.env` file for the needed app, just `npm run dev` from its directory. 26 | 27 | _After everything is set, whenever a change is made to the package in dev will reflect in the preview app when it's in dev mode._ 28 | 29 | Check out [@tinloof/sanity-studio](https://github.com/tinloof/sanity-kit/tree/main/packages/sanity-studio) for more details. 30 | -------------------------------------------------------------------------------- /apps/next/.env.example: -------------------------------------------------------------------------------- 1 | # Created by Vercel CLI 2 | NEXT_PUBLIC_SANITY_DATASET="production" 3 | NEXT_PUBLIC_SANITY_PROJECT_ID="" 4 | NEXT_PUBLIC_URL="http://localhost:3000" 5 | SANITY_API_TOKEN="" 6 | -------------------------------------------------------------------------------- /apps/next/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /apps/next/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /studio/node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | /studio/dist 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | .vscode 24 | .idea/ 25 | *.iml 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | .env*.local 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | 42 | # Env files created by scripts for working locally 43 | .env 44 | studio/.env.development 45 | -------------------------------------------------------------------------------- /apps/next/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # next-non-embedded-studio 2 | 3 | ## 1.1.13 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [558a726] 8 | - @tinloof/sanity-studio@1.10.0 9 | 10 | ## 1.1.12 11 | 12 | ### Patch Changes 13 | 14 | - Updated dependencies [4dab9d0] 15 | - @tinloof/sanity-studio@1.9.1 16 | 17 | ## 1.1.11 18 | 19 | ### Patch Changes 20 | 21 | - Updated dependencies [862b9cf] 22 | - @tinloof/sanity-studio@1.9.0 23 | 24 | ## 1.1.10 25 | 26 | ### Patch Changes 27 | 28 | - Updated dependencies [671f87b] 29 | - Updated dependencies [4b7f06a] 30 | - Updated dependencies [c32a811] 31 | - @tinloof/sanity-studio@1.8.0 32 | - @tinloof/sanity-web@0.8.0 33 | 34 | ## 1.1.9 35 | 36 | ### Patch Changes 37 | 38 | - Updated dependencies [a661a98] 39 | - @tinloof/sanity-studio@1.7.5 40 | 41 | ## 1.1.8 42 | 43 | ### Patch Changes 44 | 45 | - Updated dependencies [7df28de] 46 | - Updated dependencies [cc217b1] 47 | - @tinloof/sanity-studio@1.7.4 48 | 49 | ## 1.1.7 50 | 51 | ### Patch Changes 52 | 53 | - Updated dependencies [986ee30] 54 | - @tinloof/sanity-studio@1.7.3 55 | 56 | ## 1.1.6 57 | 58 | ### Patch Changes 59 | 60 | - Updated dependencies [4cf564d] 61 | - @tinloof/sanity-studio@1.7.2 62 | 63 | ## 1.1.5 64 | 65 | ### Patch Changes 66 | 67 | - Updated dependencies [120cb3c] 68 | - @tinloof/sanity-studio@1.7.1 69 | 70 | ## 1.1.4 71 | 72 | ### Patch Changes 73 | 74 | - Updated dependencies [281f590] 75 | - @tinloof/sanity-studio@1.7.0 76 | 77 | ## 1.1.3 78 | 79 | ### Patch Changes 80 | 81 | - Updated dependencies [070b307] 82 | - @tinloof/sanity-studio@1.6.1 83 | 84 | ## 1.1.2 85 | 86 | ### Patch Changes 87 | 88 | - Updated dependencies [fe72e4f] 89 | - @tinloof/sanity-studio@1.6.0 90 | 91 | ## 1.1.1 92 | 93 | ### Patch Changes 94 | 95 | - Updated dependencies [3fe5ab5] 96 | - Updated dependencies [164b8db] 97 | - Updated dependencies [aa66ce9] 98 | - @tinloof/sanity-studio@1.5.0 99 | - @tinloof/sanity-web@0.7.0 100 | 101 | ## 1.1.0 102 | 103 | ### Minor Changes 104 | 105 | - 1f6a3b8: Support Next 15 and React 19 106 | 107 | ### Patch Changes 108 | 109 | - 85e2320: Improve sections 110 | - Updated dependencies [85e2320] 111 | - Updated dependencies [21c7dc8] 112 | - Updated dependencies [1f6a3b8] 113 | - @tinloof/sanity-studio@1.4.0 114 | - @tinloof/sanity-web@0.6.0 115 | 116 | ## null 117 | 118 | ### Patch Changes 119 | 120 | - Updated dependencies [89b54ad] 121 | - Updated dependencies [454a00f] 122 | - @tinloof/sanity-studio@1.3.4 123 | - @tinloof/sanity-web@0.4.3 124 | 125 | ## null 126 | 127 | ### Patch Changes 128 | 129 | - Updated dependencies [8059797] 130 | - @tinloof/sanity-studio@1.3.3 131 | 132 | ## null 133 | 134 | ### Patch Changes 135 | 136 | - Updated dependencies [754535d] 137 | - Updated dependencies [55dae45] 138 | - Updated dependencies [928c529] 139 | - @tinloof/sanity-studio@1.3.2 140 | - @tinloof/sanity-web@0.4.2 141 | 142 | ## null 143 | 144 | ### Patch Changes 145 | 146 | - Updated dependencies [95a15b3] 147 | - Updated dependencies [9e62382] 148 | - @tinloof/sanity-studio@1.3.1 149 | - @tinloof/sanity-web@0.4.1 150 | 151 | ## null 152 | 153 | ### Patch Changes 154 | 155 | - Updated dependencies [2a263d7] 156 | - Updated dependencies [a09d24f] 157 | - Updated dependencies [29a848a] 158 | - Updated dependencies [4289934] 159 | - Updated dependencies [4c92cc8] 160 | - Updated dependencies [ac331cf] 161 | - Updated dependencies [7dd9046] 162 | - Updated dependencies [ab53988] 163 | - @tinloof/sanity-studio@1.3.0 164 | - @tinloof/sanity-web@0.4.0 165 | 166 | ## null 167 | 168 | ### Patch Changes 169 | 170 | - Updated dependencies [a3677bb] 171 | - @tinloof/sanity-studio@1.2.1 172 | 173 | ## null 174 | 175 | ### Patch Changes 176 | 177 | - Updated dependencies [08efb47] 178 | - Updated dependencies [e5aa2a8] 179 | - Updated dependencies [8575999] 180 | - @tinloof/sanity-studio@1.2.0 181 | - @tinloof/sanity-web@0.3.1 182 | 183 | ## null 184 | 185 | ### Patch Changes 186 | 187 | - Updated dependencies [c96e9f7] 188 | - @tinloof/sanity-studio@1.1.3 189 | 190 | ## null 191 | 192 | ### Patch Changes 193 | 194 | - Updated dependencies [0696902] 195 | - @tinloof/sanity-web@0.3.0 196 | - @tinloof/sanity-studio@1.1.2 197 | 198 | ## null 199 | 200 | ### Patch Changes 201 | 202 | - Updated dependencies [5fe8baf] 203 | - @tinloof/sanity-web@0.2.1 204 | - @tinloof/sanity-studio@1.1.1 205 | 206 | ## null 207 | 208 | ### Patch Changes 209 | 210 | - Updated dependencies [e48e6e5] 211 | - Updated dependencies [e48e6e5] 212 | - Updated dependencies [ec602d8] 213 | - @tinloof/sanity-web@0.2.0 214 | - @tinloof/sanity-studio@1.1.0 215 | 216 | ## null 217 | 218 | ### Patch Changes 219 | 220 | - Updated dependencies [5beeb0a] 221 | - @tinloof/sanity-studio@1.0.2 222 | 223 | ## null 224 | 225 | ### Patch Changes 226 | 227 | - Updated dependencies [b442ca5] 228 | - @tinloof/sanity-studio@1.0.1 229 | 230 | ## null 231 | 232 | ### Patch Changes 233 | 234 | - Updated dependencies [3899f1a] 235 | - @tinloof/sanity-studio@1.0.0 236 | 237 | ## null 238 | 239 | ### Patch Changes 240 | 241 | - Updated dependencies [a09953c] 242 | - @tinloof/sanity-studio@0.4.1 243 | -------------------------------------------------------------------------------- /apps/next/README.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | 3 | Minimalist template of a Next.js website managed from Sanity. 4 | 5 | ![Studio Overview](./public/studio.png) 6 | 7 | ## Running the project locally 8 | 9 | ``` 10 | npm run dev 11 | ``` 12 | 13 | ## Set up 14 | 15 | ### 1. Set up the environment 16 | 17 | Copy `.env.example` and fill it up with your Sanity project variables: 18 | 19 | ``` 20 | cp .env.example .env 21 | ``` 22 | 23 | Alternatively, if you're deploying on Vercel, run the following: 24 | 25 | ``` 26 | vercel link 27 | vercel env pull .env 28 | ``` 29 | 30 | ### 2. Install dependencies 31 | 32 | ``` 33 | npm i 34 | ``` 35 | -------------------------------------------------------------------------------- /apps/next/app/(website)/[...path]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from '@/components/Page' 2 | import { loadPage } from '@/data/sanity' 3 | 4 | export default async function DynamicRoute({ 5 | params, 6 | }: { 7 | params: Promise<{ path: string[] }> 8 | }) { 9 | const pathname = `/${(await params).path.join('/')}` 10 | const data = await loadPage(pathname) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /apps/next/app/(website)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next' 2 | import { VisualEditing } from 'next-sanity' 3 | import { revalidatePath, revalidateTag } from 'next/cache' 4 | import { draftMode } from 'next/headers' 5 | 6 | import config from '@/config' 7 | 8 | export const metadata: Metadata = { 9 | title: `${config.siteName} - Website`, 10 | } 11 | 12 | export default async function Layout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | const isDraftModeEnabled = (await draftMode()).isEnabled 18 | return ( 19 | <> 20 | {children} 21 | {isDraftModeEnabled && ( 22 | { 24 | 'use server' 25 | if (!isDraftModeEnabled) { 26 | console.debug( 27 | 'Skipped manual refresh because draft mode is not enabled', 28 | ) 29 | return 30 | } 31 | if (payload.source === 'mutation') { 32 | if (payload.document.slug?.current) { 33 | const tag = `${payload.document._type}:${payload.document.slug.current}` 34 | console.log('Revalidate slug', tag) 35 | await revalidateTag(tag) 36 | } 37 | console.log('Revalidate tag', payload.document._type) 38 | return revalidateTag(payload.document._type) 39 | } 40 | await revalidatePath('/', 'layout') 41 | }} 42 | /> 43 | )} 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /apps/next/app/(website)/page.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from '@/components/Page' 2 | import { loadPage } from '@/data/sanity' 3 | 4 | export default async function IndexRoute() { 5 | const data = await loadPage('/') 6 | 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /apps/next/app/api/disable-draft/route.ts: -------------------------------------------------------------------------------- 1 | import { draftMode } from 'next/headers' 2 | import { NextRequest, NextResponse } from 'next/server' 3 | 4 | export async function GET(request: NextRequest) { 5 | ;(await draftMode()).disable() 6 | const url = new URL(request.nextUrl) 7 | return NextResponse.redirect(new URL('/', url.origin)) 8 | } 9 | -------------------------------------------------------------------------------- /apps/next/app/api/draft/route.ts: -------------------------------------------------------------------------------- 1 | import { validatePreviewUrl } from '@sanity/preview-url-secret' 2 | import { draftMode } from 'next/headers' 3 | import { redirect } from 'next/navigation' 4 | 5 | import config from '@/config' 6 | import { client } from '@/data/sanity/client' 7 | 8 | const clientWithToken = client.withConfig({ token: config.sanity.token }) 9 | 10 | export async function GET(request: Request) { 11 | const { isValid, redirectTo = '/' } = await validatePreviewUrl( 12 | clientWithToken, 13 | request.url, 14 | ) 15 | if (!isValid) { 16 | return new Response('Invalid secret', { status: 401 }) 17 | } 18 | 19 | ;(await draftMode()).enable() 20 | 21 | redirect(redirectTo) 22 | } 23 | -------------------------------------------------------------------------------- /apps/next/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/apps/next/app/favicon.ico -------------------------------------------------------------------------------- /apps/next/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/apps/next/app/icon.png -------------------------------------------------------------------------------- /apps/next/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import 'tailwindcss/tailwind.css' 2 | 3 | import { Inter } from 'next/font/google' 4 | 5 | const sans = Inter({ 6 | variable: '--font-sans', 7 | subsets: ['latin'], 8 | }) 9 | 10 | export default async function RootLayout({ 11 | children, 12 | }: { 13 | children: React.ReactNode 14 | }) { 15 | return ( 16 | 17 | {children} 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /apps/next/components/Page.tsx: -------------------------------------------------------------------------------- 1 | import type { PagePayload } from '@/types' 2 | import { SanityImage } from './SanityImage' 3 | 4 | export interface PageProps { 5 | data: PagePayload | null 6 | } 7 | 8 | export function Page({ data }: PageProps) { 9 | const { title } = data ?? {} 10 | 11 | return ( 12 |
13 |

{title}

14 |
15 | {data?.image && } 16 |
17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /apps/next/components/SanityImage.tsx: -------------------------------------------------------------------------------- 1 | import type { SanityImageProps } from '@tinloof/sanity-web' 2 | 3 | import { SanityImage as SanityImageBase } from '@tinloof/sanity-web' 4 | 5 | import config from '@/config' 6 | 7 | export function SanityImage({ 8 | data, 9 | ...props 10 | }: Omit) { 11 | return ( 12 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /apps/next/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | sanity: { 3 | projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || '', 4 | dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || '', 5 | // Not exposed to the front-end, used solely by the server 6 | token: process.env.SANITY_API_TOKEN || '', 7 | apiVersion: process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-06-21', 8 | revalidateSecret: process.env.SANITY_REVALIDATE_SECRET || '', 9 | studioUrl: '/studio', 10 | }, 11 | siteDomain: process.env.NEXT_PUBLIC_SITE_DOMAIN || '', 12 | baseUrl: process.env.NEXT_PUBLIC_BASE_URL || '', 13 | } 14 | 15 | export default config 16 | -------------------------------------------------------------------------------- /apps/next/data/sanity/client.ts: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | import { ClientPerspective, createClient } from 'next-sanity' 3 | 4 | const clientConfig = { 5 | projectId: config.sanity.projectId, 6 | dataset: config.sanity.dataset, 7 | apiVersion: config.sanity.apiVersion, 8 | useCdn: process.env.NODE_ENV === 'production', 9 | perspective: 'published' as ClientPerspective, 10 | } 11 | 12 | export const client = createClient({ 13 | ...clientConfig, 14 | stega: { 15 | studioUrl: config.sanity.studioUrl, 16 | // logger: console, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /apps/next/data/sanity/generateStaticSlugs.ts: -------------------------------------------------------------------------------- 1 | import 'server-only' 2 | 3 | import { groq } from 'next-sanity' 4 | 5 | import config from '@/config' 6 | import { client } from '@/data/sanity/client' 7 | 8 | // Used in `generateStaticParams` 9 | export function generateStaticPaths(types: string[]) { 10 | return client 11 | .withConfig({ 12 | token: config.sanity.token, 13 | perspective: 'published', 14 | useCdn: false, 15 | stega: false, 16 | }) 17 | .fetch( 18 | groq`*[_type in $types && defined(pathname.current)][].pathname.current`, 19 | { types }, 20 | { 21 | next: { 22 | tags: types, 23 | }, 24 | }, 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /apps/next/data/sanity/index.ts: -------------------------------------------------------------------------------- 1 | import { PagePayload } from '@/types' 2 | import { loadQuery } from './loadQuery' 3 | import { PAGE_QUERY } from './queries' 4 | 5 | export async function loadPage(pathname: string) { 6 | return loadQuery({ 7 | query: PAGE_QUERY, 8 | params: { pathname }, 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /apps/next/data/sanity/loadQuery.ts: -------------------------------------------------------------------------------- 1 | import type { UnfilteredResponseQueryOptions } from '@sanity/client' 2 | import type { QueryParams } from 'next-sanity' 3 | 4 | import { draftMode } from 'next/headers' 5 | import 'server-only' 6 | 7 | import config from '@/config' 8 | import { client } from '@/data/sanity/client' 9 | 10 | const DEFAULT_PARAMS = {} as QueryParams 11 | 12 | export async function loadQuery({ 13 | query, 14 | params = DEFAULT_PARAMS, 15 | }: { 16 | query: string 17 | params?: QueryParams 18 | }): Promise { 19 | const isDraftMode = (await draftMode()).isEnabled 20 | const token = config.sanity.token 21 | 22 | if (isDraftMode && !token) { 23 | throw new Error( 24 | 'The `SANITY_API_READ_TOKEN` environment variable is required in Draft Mode.', 25 | ) 26 | } 27 | 28 | // https://nextjs.org/docs/app/api-reference/functions/fetch#optionsnextrevalidate 29 | /* 30 | const REVALIDATE_SKIP_CACHE = 0 31 | const REVALIDATE_CACHE_FOREVER = false 32 | const revalidate = ( 33 | isDraftMode 34 | ? // If we're in Draft Mode we want fresh content on every request so we skip the cache 35 | REVALIDATE_SKIP_CACHE 36 | : revalidateSecret 37 | ? // If GROQ webhook revalidation is setup, then we only want to revalidate on-demand so the cache lives as long as possible 38 | REVALIDATE_CACHE_FOREVER 39 | : // No webhook means we don't know ahead of time when content changes, so we use the Sanity CDN API cache which has its own Stale-While-Revalidate logic 40 | REVALIDATE_SKIP_CACHE 41 | ) satisfies NextFetchRequestConfig['revalidate'] 42 | // */ 43 | 44 | const perspective = isDraftMode ? 'previewDrafts' : 'published' 45 | 46 | const options = { 47 | filterResponse: false, 48 | useCdn: false, 49 | resultSourceMap: isDraftMode ? 'withKeyArraySelector' : false, 50 | token: isDraftMode ? token : undefined, 51 | perspective, 52 | next: { 53 | tags: ['sanity'], 54 | // Cache forever in Draft Mode until expired with revalidateTag, use time-based cache in production that matches the CDN cache 55 | revalidate: isDraftMode ? false : 60, 56 | }, 57 | } satisfies UnfilteredResponseQueryOptions 58 | const result = await client.fetch(query, params, { 59 | ...options, 60 | stega: isDraftMode, 61 | } as UnfilteredResponseQueryOptions) 62 | return result.result 63 | } 64 | -------------------------------------------------------------------------------- /apps/next/data/sanity/queries.ts: -------------------------------------------------------------------------------- 1 | import { groq } from 'next-sanity' 2 | 3 | export const PAGE_QUERY = groq`*[pathname.current == $pathname][0]` 4 | -------------------------------------------------------------------------------- /apps/next/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/next/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const config = { 3 | images: { 4 | remotePatterns: [{ hostname: 'cdn.sanity.io' }], 5 | }, 6 | typescript: { 7 | // Set this to false if you want production builds to abort if there's type errors 8 | ignoreBuildErrors: process.env.VERCEL_ENV === 'production', 9 | }, 10 | eslint: { 11 | /// Set this to false if you want production builds to abort if there's lint errors 12 | ignoreDuringBuilds: process.env.VERCEL_ENV === 'production', 13 | }, 14 | logging: { 15 | fetches: { 16 | fullUrl: true, 17 | }, 18 | }, 19 | experimental: { 20 | taint: true, 21 | }, 22 | } 23 | 24 | export default config 25 | -------------------------------------------------------------------------------- /apps/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-non-embedded-studio", 3 | "private": true, 4 | "scripts": { 5 | "build": "next build", 6 | "dev": "next -p 9999 --turbo", 7 | "format": "npx prettier --write . --ignore-path .gitignore", 8 | "lint": "next lint -- --ignore-path .gitignore", 9 | "lint:fix": "npm run format && npm run lint -- --fix", 10 | "start": "next start", 11 | "type-check": "tsc --noEmit" 12 | }, 13 | "prettier": { 14 | "semi": false, 15 | "singleQuote": true 16 | }, 17 | "dependencies": { 18 | "@sanity/client": "^6.27.2", 19 | "@sanity/preview-url-secret": "^2.1.4", 20 | "@sanity/react-loader": "^1.10.41", 21 | "@sanity/vision": "^3.74.0", 22 | "@tailwindcss/typography": "0.5.15", 23 | "@tinloof/sanity-studio": "workspace:*", 24 | "@tinloof/sanity-web": "workspace:*", 25 | "classnames": "2.5.1", 26 | "lucide-react": "^0.453.0", 27 | "next": "15.2.3", 28 | "next-sanity": "^9.8.51", 29 | "react": "^19.0.0", 30 | "react-dom": "^19.0.0", 31 | "sanity": "^3.80.1", 32 | "server-only": "0.0.1" 33 | }, 34 | "devDependencies": { 35 | "@types/react": "^18.3.11", 36 | "@types/react-dom": "^18.3.1", 37 | "autoprefixer": "10.4.20", 38 | "eslint": "^8.57.0", 39 | "eslint-config-next": "15.1.1", 40 | "eslint-config-prettier": "^9.1.0", 41 | "eslint-plugin-prettier": "^5.2.1", 42 | "eslint-plugin-simple-import-sort": "12.1.1", 43 | "postcss": "8.4.47", 44 | "prettier": "^3.3.3", 45 | "prettier-plugin-packagejson": "^2.5.3", 46 | "prettier-plugin-tailwindcss": "0.6.8", 47 | "tailwindcss": "3.4.14", 48 | "typescript": "^5.6.3" 49 | }, 50 | "version": "1.1.13" 51 | } 52 | -------------------------------------------------------------------------------- /apps/next/postcss.config.js: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /apps/next/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/apps/next/public/logo.png -------------------------------------------------------------------------------- /apps/next/public/studio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/apps/next/public/studio.png -------------------------------------------------------------------------------- /apps/next/styles/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | -webkit-font-smoothing: antialiased; 3 | -moz-osx-font-smoothing: grayscale; 4 | overflow-x: hidden; 5 | } 6 | 7 | p:not(:last-child) { 8 | margin-bottom: 0.875rem; 9 | } 10 | 11 | ol, 12 | ul { 13 | margin-left: 1rem; 14 | } 15 | 16 | ol { 17 | list-style-type: disc; 18 | } 19 | -------------------------------------------------------------------------------- /apps/next/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | content: [ 6 | './app/**/*.{js,ts,jsx,tsx}', 7 | './components/**/*.{js,ts,jsx,tsx}', 8 | './intro-template/**/*.{js,ts,jsx,tsx}', 9 | ], 10 | theme: { 11 | ...defaultTheme, 12 | // Overriding fontFamily to use @next/font loaded families 13 | fontFamily: { 14 | mono: 'var(--font-mono)', 15 | sans: 'var(--font-sans)', 16 | serif: 'var(--font-serif)', 17 | }, 18 | }, 19 | plugins: [require('@tailwindcss/typography')], 20 | } 21 | -------------------------------------------------------------------------------- /apps/next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "strictNullChecks": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "paths": { 24 | "@/*": ["./*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /apps/next/types/index.ts: -------------------------------------------------------------------------------- 1 | export type PagePayload = { 2 | _id: string 3 | _type: string 4 | pathname: string 5 | title?: string 6 | image?: any 7 | } 8 | -------------------------------------------------------------------------------- /apps/studio/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/eslint-config-studio" 3 | } 4 | -------------------------------------------------------------------------------- /apps/studio/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # Compiled Sanity Studio 9 | /dist 10 | 11 | # Temporary Sanity runtime, generated by the CLI on every dev server start 12 | /.sanity 13 | 14 | # Logs 15 | /logs 16 | *.log 17 | 18 | # Coverage directory used by testing tools 19 | /coverage 20 | 21 | # Misc 22 | .DS_Store 23 | *.pem 24 | 25 | # Typescript 26 | *.tsbuildinfo 27 | 28 | # Dotenv and similar local-only files 29 | *.local 30 | -------------------------------------------------------------------------------- /apps/studio/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # vite-studio 2 | 3 | ## 1.1.13 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [558a726] 8 | - @tinloof/sanity-studio@1.10.0 9 | 10 | ## 1.1.12 11 | 12 | ### Patch Changes 13 | 14 | - Updated dependencies [4dab9d0] 15 | - @tinloof/sanity-studio@1.9.1 16 | 17 | ## 1.1.11 18 | 19 | ### Patch Changes 20 | 21 | - Updated dependencies [862b9cf] 22 | - @tinloof/sanity-studio@1.9.0 23 | 24 | ## 1.1.10 25 | 26 | ### Patch Changes 27 | 28 | - Updated dependencies [671f87b] 29 | - Updated dependencies [4b7f06a] 30 | - Updated dependencies [c32a811] 31 | - @tinloof/sanity-studio@1.8.0 32 | 33 | ## 1.1.9 34 | 35 | ### Patch Changes 36 | 37 | - Updated dependencies [a661a98] 38 | - @tinloof/sanity-studio@1.7.5 39 | 40 | ## 1.1.8 41 | 42 | ### Patch Changes 43 | 44 | - Updated dependencies [7df28de] 45 | - Updated dependencies [cc217b1] 46 | - @tinloof/sanity-studio@1.7.4 47 | 48 | ## 1.1.7 49 | 50 | ### Patch Changes 51 | 52 | - Updated dependencies [986ee30] 53 | - @tinloof/sanity-studio@1.7.3 54 | 55 | ## 1.1.6 56 | 57 | ### Patch Changes 58 | 59 | - Updated dependencies [4cf564d] 60 | - @tinloof/sanity-studio@1.7.2 61 | 62 | ## 1.1.5 63 | 64 | ### Patch Changes 65 | 66 | - Updated dependencies [120cb3c] 67 | - @tinloof/sanity-studio@1.7.1 68 | 69 | ## 1.1.4 70 | 71 | ### Patch Changes 72 | 73 | - Updated dependencies [281f590] 74 | - @tinloof/sanity-studio@1.7.0 75 | 76 | ## 1.1.3 77 | 78 | ### Patch Changes 79 | 80 | - Updated dependencies [070b307] 81 | - @tinloof/sanity-studio@1.6.1 82 | 83 | ## 1.1.2 84 | 85 | ### Patch Changes 86 | 87 | - Updated dependencies [fe72e4f] 88 | - @tinloof/sanity-studio@1.6.0 89 | 90 | ## 1.1.1 91 | 92 | ### Patch Changes 93 | 94 | - Updated dependencies [3fe5ab5] 95 | - Updated dependencies [164b8db] 96 | - Updated dependencies [aa66ce9] 97 | - @tinloof/sanity-studio@1.5.0 98 | 99 | ## 1.1.0 100 | 101 | ### Minor Changes 102 | 103 | - 1f6a3b8: Support Next 15 and React 19 104 | 105 | ### Patch Changes 106 | 107 | - 85e2320: Improve sections 108 | - Updated dependencies [85e2320] 109 | - Updated dependencies [21c7dc8] 110 | - Updated dependencies [1f6a3b8] 111 | - @tinloof/sanity-studio@1.4.0 112 | 113 | ## 1.0.16 114 | 115 | ### Patch Changes 116 | 117 | - @tinloof/sanity-studio@1.3.5 118 | 119 | ## 1.0.15 120 | 121 | ### Patch Changes 122 | 123 | - Updated dependencies [89b54ad] 124 | - Updated dependencies [454a00f] 125 | - @tinloof/sanity-studio@1.3.4 126 | 127 | ## 1.0.14 128 | 129 | ### Patch Changes 130 | 131 | - Updated dependencies [8059797] 132 | - @tinloof/sanity-studio@1.3.3 133 | 134 | ## 1.0.13 135 | 136 | ### Patch Changes 137 | 138 | - Updated dependencies [754535d] 139 | - Updated dependencies [55dae45] 140 | - Updated dependencies [928c529] 141 | - @tinloof/sanity-studio@1.3.2 142 | 143 | ## 1.0.12 144 | 145 | ### Patch Changes 146 | 147 | - Updated dependencies [95a15b3] 148 | - Updated dependencies [9e62382] 149 | - @tinloof/sanity-studio@1.3.1 150 | 151 | ## 1.0.11 152 | 153 | ### Patch Changes 154 | 155 | - Updated dependencies [2a263d7] 156 | - Updated dependencies [a09d24f] 157 | - Updated dependencies [29a848a] 158 | - Updated dependencies [4289934] 159 | - Updated dependencies [4c92cc8] 160 | - Updated dependencies [ac331cf] 161 | - Updated dependencies [7dd9046] 162 | - Updated dependencies [ab53988] 163 | - @tinloof/sanity-studio@1.3.0 164 | 165 | ## 1.0.10 166 | 167 | ### Patch Changes 168 | 169 | - Updated dependencies [a3677bb] 170 | - @tinloof/sanity-studio@1.2.1 171 | 172 | ## 1.0.9 173 | 174 | ### Patch Changes 175 | 176 | - Updated dependencies [08efb47] 177 | - Updated dependencies [e5aa2a8] 178 | - @tinloof/sanity-studio@1.2.0 179 | 180 | ## 1.0.8 181 | 182 | ### Patch Changes 183 | 184 | - Updated dependencies [c96e9f7] 185 | - @tinloof/sanity-studio@1.1.3 186 | 187 | ## 1.0.7 188 | 189 | ### Patch Changes 190 | 191 | - @tinloof/sanity-studio@1.1.2 192 | 193 | ## 1.0.6 194 | 195 | ### Patch Changes 196 | 197 | - @tinloof/sanity-studio@1.1.1 198 | 199 | ## 1.0.5 200 | 201 | ### Patch Changes 202 | 203 | - Updated dependencies [e48e6e5] 204 | - Updated dependencies [ec602d8] 205 | - @tinloof/sanity-studio@1.1.0 206 | 207 | ## 1.0.4 208 | 209 | ### Patch Changes 210 | 211 | - Updated dependencies [5beeb0a] 212 | - @tinloof/sanity-studio@1.0.2 213 | 214 | ## 1.0.3 215 | 216 | ### Patch Changes 217 | 218 | - Updated dependencies [b442ca5] 219 | - @tinloof/sanity-studio@1.0.1 220 | 221 | ## 1.0.2 222 | 223 | ### Patch Changes 224 | 225 | - Updated dependencies [3899f1a] 226 | - @tinloof/sanity-studio@1.0.0 227 | 228 | ## 1.0.1 229 | 230 | ### Patch Changes 231 | 232 | - Updated dependencies [a09953c] 233 | - @tinloof/sanity-studio@0.4.1 234 | -------------------------------------------------------------------------------- /apps/studio/README.md: -------------------------------------------------------------------------------- 1 | # Sanity Clean Content Studio 2 | 3 | Congratulations, you have now installed the Sanity Content Studio, an open source real-time content editing environment connected to the Sanity backend. 4 | 5 | Now you can do the following things: 6 | 7 | - [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme) 8 | - [Join the community Slack](https://slack.sanity.io/?utm_source=readme) 9 | - [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme) 10 | -------------------------------------------------------------------------------- /apps/studio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-studio", 3 | "private": true, 4 | "version": "1.1.13", 5 | "main": "package.json", 6 | "license": "UNLICENSED", 7 | "scripts": { 8 | "dev": "sanity dev", 9 | "start": "sanity start", 10 | "build": "sanity build", 11 | "deploy": "sanity deploy", 12 | "deploy-graphql": "sanity graphql deploy" 13 | }, 14 | "keywords": [ 15 | "sanity" 16 | ], 17 | "dependencies": { 18 | "@sanity/vision": "^3.80.1", 19 | "@tinloof/sanity-studio": "workspace:*", 20 | "react": "^18.3.1", 21 | "react-dom": "^18.3.1", 22 | "react-is": "^18.3.1", 23 | "sanity": "^3.80.1", 24 | "styled-components": "^6.1.15" 25 | }, 26 | "devDependencies": { 27 | "@sanity/eslint-config-studio": "^4.0.0", 28 | "@sanity/types": "^3.80.1", 29 | "@types/react": "^18.3.11", 30 | "eslint": "^8.57.0", 31 | "prettier": "^3.3.3", 32 | "typescript": "^5.6.3" 33 | }, 34 | "prettier": { 35 | "semi": false, 36 | "printWidth": 100, 37 | "bracketSpacing": false, 38 | "singleQuote": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /apps/studio/sanity.cli.ts: -------------------------------------------------------------------------------- 1 | import {defineCliConfig} from 'sanity/cli' 2 | 3 | export default defineCliConfig({ 4 | api: { 5 | projectId: 'ptjmyfc9', 6 | dataset: 'production', 7 | }, 8 | }) 9 | -------------------------------------------------------------------------------- /apps/studio/sanity.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'sanity' 2 | import {structureTool} from 'sanity/structure' 3 | import {visionTool} from '@sanity/vision' 4 | import {schemaTypes} from './schemas' 5 | import {pages} from '@tinloof/sanity-studio' 6 | 7 | export default defineConfig({ 8 | name: 'default', 9 | title: 'Vite studio', 10 | projectId: 'qfrmq8mg', 11 | dataset: 'production', 12 | plugins: [ 13 | structureTool(), 14 | pages({ 15 | previewUrl: { 16 | previewMode: { 17 | enable: 'http://localhost:9999/api/draft', 18 | }, 19 | }, 20 | creatablePages: ['page', 'post', 'author'], 21 | }), 22 | visionTool(), 23 | ], 24 | schema: { 25 | types: schemaTypes, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /apps/studio/schemas/author.ts: -------------------------------------------------------------------------------- 1 | import {definePathname} from '@tinloof/sanity-studio' 2 | import {defineType} from 'sanity' 3 | 4 | export default defineType({ 5 | type: 'document', 6 | name: 'author', 7 | fields: [ 8 | { 9 | type: 'string', 10 | name: 'title', 11 | }, 12 | definePathname({ 13 | name: 'pathname', 14 | initialValue: { 15 | current: '/authors/', 16 | }, 17 | options: {folder: {canUnlock: false}}, 18 | }), 19 | ], 20 | }) 21 | -------------------------------------------------------------------------------- /apps/studio/schemas/index.ts: -------------------------------------------------------------------------------- 1 | import author from './author' 2 | import page from './page' 3 | import post from './post' 4 | 5 | export const schemaTypes = [page, post, author] 6 | -------------------------------------------------------------------------------- /apps/studio/schemas/page.ts: -------------------------------------------------------------------------------- 1 | import {definePathname} from '@tinloof/sanity-studio' 2 | import {defineType} from 'sanity' 3 | 4 | export default defineType({ 5 | type: 'document', 6 | name: 'page', 7 | fields: [ 8 | { 9 | type: 'string', 10 | name: 'title', 11 | }, 12 | { 13 | type: 'image', 14 | name: 'image', 15 | options: { 16 | hotspot: true, 17 | }, 18 | }, 19 | definePathname({name: 'pathname'}), 20 | ], 21 | }) 22 | -------------------------------------------------------------------------------- /apps/studio/schemas/post.ts: -------------------------------------------------------------------------------- 1 | import {definePathname} from '@tinloof/sanity-studio' 2 | import {defineType} from 'sanity' 3 | 4 | export default defineType({ 5 | type: 'document', 6 | name: 'post', 7 | fields: [ 8 | { 9 | type: 'string', 10 | name: 'title', 11 | }, 12 | definePathname({ 13 | name: 'pathname', 14 | initialValue: { 15 | current: '/blog/', 16 | }, 17 | options: {folder: {canUnlock: false}}, 18 | }), 19 | ], 20 | }) 21 | -------------------------------------------------------------------------------- /apps/studio/static/.gitkeep: -------------------------------------------------------------------------------- 1 | Files placed here will be served by the Sanity server under the `/static`-prefix 2 | -------------------------------------------------------------------------------- /apps/studio/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/hello-world/.env.example: -------------------------------------------------------------------------------- 1 | # Created by Vercel CLI 2 | NEXT_PUBLIC_SANITY_DATASET="production" 3 | NEXT_PUBLIC_SANITY_PROJECT_ID="" 4 | NEXT_PUBLIC_URL="http://localhost:3000" 5 | SANITY_API_TOKEN="" 6 | -------------------------------------------------------------------------------- /examples/hello-world/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/hello-world/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /studio/node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | /studio/dist 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | .vscode 24 | .idea/ 25 | *.iml 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | .env*.local 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | 42 | # Env files created by scripts for working locally 43 | .env 44 | studio/.env.development 45 | -------------------------------------------------------------------------------- /examples/hello-world/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # hello-world 2 | 3 | ## null 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [89b54ad] 8 | - Updated dependencies [454a00f] 9 | - @tinloof/sanity-studio@1.3.4 10 | 11 | ## null 12 | 13 | ### Patch Changes 14 | 15 | - Updated dependencies [8059797] 16 | - @tinloof/sanity-studio@1.3.3 17 | 18 | ## null 19 | 20 | ### Patch Changes 21 | 22 | - Updated dependencies [754535d] 23 | - Updated dependencies [55dae45] 24 | - Updated dependencies [928c529] 25 | - @tinloof/sanity-studio@1.3.2 26 | 27 | ## null 28 | 29 | ### Patch Changes 30 | 31 | - Updated dependencies [95a15b3] 32 | - Updated dependencies [9e62382] 33 | - @tinloof/sanity-studio@1.3.1 34 | 35 | ## null 36 | 37 | ### Patch Changes 38 | 39 | - Updated dependencies [2a263d7] 40 | - Updated dependencies [a09d24f] 41 | - Updated dependencies [29a848a] 42 | - Updated dependencies [4289934] 43 | - Updated dependencies [4c92cc8] 44 | - Updated dependencies [ac331cf] 45 | - Updated dependencies [7dd9046] 46 | - Updated dependencies [ab53988] 47 | - @tinloof/sanity-studio@1.3.0 48 | 49 | ## null 50 | 51 | ### Patch Changes 52 | 53 | - Updated dependencies [a3677bb] 54 | - @tinloof/sanity-studio@1.2.1 55 | 56 | ## null 57 | 58 | ### Patch Changes 59 | 60 | - Updated dependencies [08efb47] 61 | - Updated dependencies [e5aa2a8] 62 | - @tinloof/sanity-studio@1.2.0 63 | 64 | ## null 65 | 66 | ### Patch Changes 67 | 68 | - Updated dependencies [c96e9f7] 69 | - @tinloof/sanity-studio@1.1.3 70 | 71 | ## null 72 | 73 | ### Patch Changes 74 | 75 | - @tinloof/sanity-studio@1.1.2 76 | 77 | ## null 78 | 79 | ### Patch Changes 80 | 81 | - @tinloof/sanity-studio@1.1.1 82 | 83 | ## null 84 | 85 | ### Patch Changes 86 | 87 | - Updated dependencies [e48e6e5] 88 | - Updated dependencies [ec602d8] 89 | - @tinloof/sanity-studio@1.1.0 90 | 91 | ## null 92 | 93 | ### Patch Changes 94 | 95 | - Updated dependencies [5beeb0a] 96 | - @tinloof/sanity-studio@1.0.2 97 | 98 | ## null 99 | 100 | ### Patch Changes 101 | 102 | - Updated dependencies [b442ca5] 103 | - @tinloof/sanity-studio@1.0.1 104 | 105 | ## null 106 | 107 | ### Patch Changes 108 | 109 | - Updated dependencies [3899f1a] 110 | - @tinloof/sanity-studio@1.0.0 111 | 112 | ## null 113 | 114 | ### Patch Changes 115 | 116 | - Updated dependencies [a09953c] 117 | - @tinloof/sanity-studio@0.4.1 118 | -------------------------------------------------------------------------------- /examples/hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | 3 | Minimalist template of a Next.js website managed from Sanity. 4 | 5 | ![Studio Overview](./public/studio.png) 6 | 7 | ## Running the project locally 8 | 9 | ``` 10 | npm run dev 11 | ``` 12 | 13 | ## Set up 14 | 15 | ### 1. Set up the environment 16 | 17 | Copy `.env.example` and fill it up with your Sanity project variables: 18 | 19 | ``` 20 | cp .env.example .env 21 | ``` 22 | 23 | Alternatively, if you're deploying on Vercel, run the following: 24 | 25 | ``` 26 | vercel link 27 | vercel env pull .env 28 | ``` 29 | 30 | ### 2. Install dependencies 31 | 32 | ``` 33 | npm i 34 | ``` 35 | -------------------------------------------------------------------------------- /examples/hello-world/app/(website)/[...path]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from '@/components/Page' 2 | import { loadPage } from '@/data/sanity' 3 | 4 | export default async function DynamicRoute({ 5 | params, 6 | }: { 7 | params: Promise<{ path: string[] }> 8 | }) { 9 | const pathname = `/${(await params).path.join('/')}` 10 | const data = await loadPage(pathname) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /examples/hello-world/app/(website)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next' 2 | import { VisualEditing } from 'next-sanity' 3 | import { revalidatePath, revalidateTag } from 'next/cache' 4 | import { draftMode } from 'next/headers' 5 | 6 | import config from '@/config' 7 | 8 | export const metadata: Metadata = { 9 | title: `${config.siteName} - Website`, 10 | } 11 | 12 | export default async function Layout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | const isDraftModeEnabled = (await draftMode()).isEnabled 18 | return ( 19 | <> 20 | {children} 21 | {isDraftModeEnabled && ( 22 | { 24 | 'use server' 25 | if (!isDraftModeEnabled) { 26 | console.debug( 27 | 'Skipped manual refresh because draft mode is not enabled', 28 | ) 29 | return 30 | } 31 | if (payload.source === 'mutation') { 32 | if (payload.document.slug?.current) { 33 | const tag = `${payload.document._type}:${payload.document.slug.current}` 34 | console.log('Revalidate slug', tag) 35 | await revalidateTag(tag) 36 | } 37 | console.log('Revalidate tag', payload.document._type) 38 | return revalidateTag(payload.document._type) 39 | } 40 | await revalidatePath('/', 'layout') 41 | }} 42 | /> 43 | )} 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /examples/hello-world/app/(website)/page.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from '@/components/Page' 2 | import { loadPage } from '@/data/sanity' 3 | 4 | export default async function IndexRoute() { 5 | const data = await loadPage('/') 6 | 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /examples/hello-world/app/api/disable-draft/route.ts: -------------------------------------------------------------------------------- 1 | import { draftMode } from 'next/headers' 2 | import { NextRequest, NextResponse } from 'next/server' 3 | 4 | export async function GET(request: NextRequest) { 5 | ;(await draftMode()).disable() 6 | const url = new URL(request.nextUrl) 7 | return NextResponse.redirect(new URL('/', url.origin)) 8 | } 9 | -------------------------------------------------------------------------------- /examples/hello-world/app/api/draft/route.ts: -------------------------------------------------------------------------------- 1 | import { validatePreviewUrl } from '@sanity/preview-url-secret' 2 | import { draftMode } from 'next/headers' 3 | import { redirect } from 'next/navigation' 4 | 5 | import config from '@/config' 6 | import { client } from '@/data/sanity/client' 7 | 8 | const clientWithToken = client.withConfig({ token: config.sanity.token }) 9 | 10 | export async function GET(request: Request) { 11 | const { isValid, redirectTo = '/' } = await validatePreviewUrl( 12 | clientWithToken, 13 | request.url, 14 | ) 15 | if (!isValid) { 16 | return new Response('Invalid secret', { status: 401 }) 17 | } 18 | 19 | ;(await draftMode()).enable() 20 | 21 | redirect(redirectTo) 22 | } 23 | -------------------------------------------------------------------------------- /examples/hello-world/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/examples/hello-world/app/favicon.ico -------------------------------------------------------------------------------- /examples/hello-world/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/examples/hello-world/app/icon.png -------------------------------------------------------------------------------- /examples/hello-world/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import 'tailwindcss/tailwind.css' 2 | 3 | import { Inter } from 'next/font/google' 4 | 5 | const sans = Inter({ 6 | variable: '--font-sans', 7 | subsets: ['latin'], 8 | }) 9 | 10 | export default async function RootLayout({ 11 | children, 12 | }: { 13 | children: React.ReactNode 14 | }) { 15 | return ( 16 | 17 | {children} 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /examples/hello-world/app/studio/[[...index]]/Studio.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { NextStudio } from 'next-sanity/studio' 4 | 5 | import config from '@/sanity.config' 6 | 7 | export default function Studio() { 8 | return 9 | } 10 | -------------------------------------------------------------------------------- /examples/hello-world/app/studio/[[...index]]/page.tsx: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | import { Metadata } from 'next' 3 | import Studio from './Studio' 4 | 5 | export const dynamic = 'force-static' 6 | 7 | export const metadata: Metadata = { 8 | title: `${config.siteName} - CMS`, 9 | } 10 | 11 | export default function StudioPage() { 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /examples/hello-world/components/Page.tsx: -------------------------------------------------------------------------------- 1 | import type { PagePayload } from '@/types' 2 | 3 | export interface PageProps { 4 | data: PagePayload | null 5 | } 6 | 7 | export function Page({ data }: PageProps) { 8 | const { title } = data ?? {} 9 | 10 | return ( 11 |
12 |

{title}

13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /examples/hello-world/components/StudioLogo.tsx: -------------------------------------------------------------------------------- 1 | export default function StudioLogo() { 2 | return ( 3 | Tinloof Logo 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /examples/hello-world/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | sanity: { 3 | projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || '', 4 | dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || '', 5 | // Not exposed to the front-end, used solely by the server 6 | token: process.env.SANITY_API_TOKEN || '', 7 | apiVersion: process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-06-21', 8 | revalidateSecret: process.env.SANITY_REVALIDATE_SECRET || '', 9 | studioUrl: '/studio', 10 | }, 11 | siteName: 'Hello World', 12 | siteDomain: process.env.NEXT_PUBLIC_SITE_DOMAIN || '', 13 | baseUrl: process.env.NEXT_PUBLIC_BASE_URL || '', 14 | } 15 | 16 | export default config 17 | -------------------------------------------------------------------------------- /examples/hello-world/data/sanity/client.ts: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | import { ClientPerspective, createClient } from '@sanity/client' 3 | 4 | const clientConfig = { 5 | projectId: config.sanity.projectId, 6 | dataset: config.sanity.dataset, 7 | apiVersion: config.sanity.apiVersion, 8 | useCdn: process.env.NODE_ENV === 'production', 9 | perspective: 'published' as ClientPerspective, 10 | } 11 | 12 | export const client = createClient({ 13 | ...clientConfig, 14 | stega: { 15 | studioUrl: config.sanity.studioUrl, 16 | // logger: console, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /examples/hello-world/data/sanity/generateStaticSlugs.ts: -------------------------------------------------------------------------------- 1 | import 'server-only' 2 | 3 | import { groq } from 'next-sanity' 4 | 5 | import config from '@/config' 6 | import { client } from '@/data/sanity/client' 7 | 8 | // Used in `generateStaticParams` 9 | export function generateStaticPaths(types: string[]) { 10 | return client 11 | .withConfig({ 12 | token: config.sanity.token, 13 | perspective: 'published', 14 | useCdn: false, 15 | stega: false, 16 | }) 17 | .fetch( 18 | groq`*[_type in $types && defined(pathname.current)][].pathname.current`, 19 | { types }, 20 | { 21 | next: { 22 | tags: types, 23 | }, 24 | }, 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /examples/hello-world/data/sanity/index.ts: -------------------------------------------------------------------------------- 1 | import { PagePayload } from '@/types' 2 | import { loadQuery } from './loadQuery' 3 | import { PAGE_QUERY } from './queries' 4 | 5 | export async function loadPage(pathname: string) { 6 | return loadQuery({ 7 | query: PAGE_QUERY, 8 | params: { pathname }, 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /examples/hello-world/data/sanity/loadQuery.ts: -------------------------------------------------------------------------------- 1 | import type { UnfilteredResponseQueryOptions } from '@sanity/client' 2 | import type { QueryParams } from 'next-sanity' 3 | 4 | import { draftMode } from 'next/headers' 5 | import 'server-only' 6 | 7 | import config from '@/config' 8 | import { client } from '@/data/sanity/client' 9 | 10 | const DEFAULT_PARAMS = {} as QueryParams 11 | 12 | export async function loadQuery({ 13 | query, 14 | params = DEFAULT_PARAMS, 15 | }: { 16 | query: string 17 | params?: QueryParams 18 | }): Promise { 19 | const isDraftMode = (await draftMode()).isEnabled 20 | const token = config.sanity.token 21 | 22 | if (isDraftMode && !token) { 23 | throw new Error( 24 | 'The `SANITY_API_READ_TOKEN` environment variable is required in Draft Mode.', 25 | ) 26 | } 27 | 28 | const perspective = isDraftMode ? 'previewDrafts' : 'published' 29 | 30 | const options = { 31 | filterResponse: false, 32 | useCdn: false, 33 | resultSourceMap: isDraftMode ? 'withKeyArraySelector' : false, 34 | token: isDraftMode ? token : undefined, 35 | perspective, 36 | next: { 37 | tags: ['sanity'], 38 | revalidate: isDraftMode ? 0 : undefined, 39 | }, 40 | } satisfies UnfilteredResponseQueryOptions 41 | const result = await client.fetch(query, params, { 42 | ...options, 43 | stega: isDraftMode, 44 | } as UnfilteredResponseQueryOptions) 45 | return result.result 46 | } 47 | -------------------------------------------------------------------------------- /examples/hello-world/data/sanity/queries.ts: -------------------------------------------------------------------------------- 1 | import { groq } from 'next-sanity' 2 | 3 | export const PAGE_QUERY = groq`*[pathname.current == $pathname][0]` 4 | -------------------------------------------------------------------------------- /examples/hello-world/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /examples/hello-world/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const config = { 3 | images: { 4 | remotePatterns: [{ hostname: 'cdn.sanity.io' }], 5 | }, 6 | typescript: { 7 | // Set this to false if you want production builds to abort if there's type errors 8 | ignoreBuildErrors: process.env.VERCEL_ENV === 'production', 9 | }, 10 | eslint: { 11 | /// Set this to false if you want production builds to abort if there's lint errors 12 | ignoreDuringBuilds: process.env.VERCEL_ENV === 'production', 13 | }, 14 | logging: { 15 | fetches: { 16 | fullUrl: true, 17 | }, 18 | }, 19 | experimental: { 20 | taint: true, 21 | }, 22 | } 23 | 24 | export default config 25 | -------------------------------------------------------------------------------- /examples/hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "private": true, 4 | "scripts": { 5 | "build": "next build", 6 | "dev": "next --turbo", 7 | "format": "npx prettier --write . --ignore-path .gitignore", 8 | "lint": "next lint -- --ignore-path .gitignore", 9 | "lint:fix": "npm run format && npm run lint -- --fix", 10 | "start": "next start", 11 | "type-check": "tsc --noEmit" 12 | }, 13 | "prettier": { 14 | "semi": false, 15 | "singleQuote": true 16 | }, 17 | "dependencies": { 18 | "@sanity/client": "^6.27.2", 19 | "@sanity/preview-url-secret": "^2.1.4", 20 | "@sanity/vision": "^3.74.0", 21 | "@tailwindcss/typography": "0.5.15", 22 | "@tinloof/sanity-studio": "workspace:*", 23 | "classnames": "2.5.1", 24 | "lucide-react": "^0.453.0", 25 | "next": "15.2.3", 26 | "next-sanity": "^9.8.51", 27 | "react": "^18.3.1", 28 | "react-dom": "^18.3.1", 29 | "sanity": "^3.80.1", 30 | "server-only": "0.0.1" 31 | }, 32 | "devDependencies": { 33 | "@types/react": "^18.3.11", 34 | "@types/react-dom": "^18.3.1", 35 | "autoprefixer": "10.4.20", 36 | "eslint": "^8.57.0", 37 | "eslint-config-next": "15.1.1", 38 | "eslint-config-prettier": "^9.1.0", 39 | "eslint-plugin-prettier": "^5.2.1", 40 | "eslint-plugin-simple-import-sort": "12.1.1", 41 | "postcss": "8.4.47", 42 | "prettier": "^3.3.3", 43 | "prettier-plugin-packagejson": "^2.5.3", 44 | "prettier-plugin-tailwindcss": "0.6.8", 45 | "tailwindcss": "3.4.14", 46 | "typescript": "^5.6.3" 47 | }, 48 | "version": null 49 | } 50 | -------------------------------------------------------------------------------- /examples/hello-world/postcss.config.js: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /examples/hello-world/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/examples/hello-world/public/logo.png -------------------------------------------------------------------------------- /examples/hello-world/public/studio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/examples/hello-world/public/studio.png -------------------------------------------------------------------------------- /examples/hello-world/sanity.cli.ts: -------------------------------------------------------------------------------- 1 | import { loadEnvConfig } from '@next/env' 2 | import { defineCliConfig } from 'sanity/cli' 3 | 4 | const dev = process.env.NODE_ENV !== 'production' 5 | loadEnvConfig(__dirname, dev, { info: () => null, error: console.error }) 6 | 7 | // @TODO report top-level await bug 8 | // Using a dynamic import here as `loadEnvConfig` needs to run before this file is loaded 9 | // const { projectId, dataset } = await import('@/lib/sanity.api') 10 | const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID 11 | const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET 12 | 13 | export default defineCliConfig({ api: { projectId, dataset } }) 14 | -------------------------------------------------------------------------------- /examples/hello-world/sanity.config.ts: -------------------------------------------------------------------------------- 1 | import schemas from '@/sanity/schemas' 2 | import { visionTool } from '@sanity/vision' 3 | import { pages } from '@tinloof/sanity-studio' 4 | import { defineConfig } from 'sanity' 5 | 6 | import { structureTool } from 'sanity/structure' 7 | import StudioLogo from './components/StudioLogo' 8 | import config from './config' 9 | 10 | export default defineConfig({ 11 | basePath: config.sanity.studioUrl, 12 | projectId: config.sanity.projectId, 13 | dataset: config.sanity.dataset, 14 | title: config.siteName, 15 | icon: StudioLogo, 16 | schema: { 17 | types: schemas, 18 | }, 19 | plugins: [ 20 | pages({ 21 | previewUrl: { 22 | previewMode: { 23 | enable: '/api/draft', 24 | }, 25 | }, 26 | creatablePages: ['page'], 27 | }), 28 | structureTool(), 29 | visionTool({ defaultApiVersion: config.sanity.apiVersion }), 30 | ], 31 | }) 32 | -------------------------------------------------------------------------------- /examples/hello-world/sanity/schemas/index.ts: -------------------------------------------------------------------------------- 1 | import page from './page' 2 | 3 | const schemas = [page] 4 | 5 | export default schemas 6 | -------------------------------------------------------------------------------- /examples/hello-world/sanity/schemas/page.tsx: -------------------------------------------------------------------------------- 1 | import { definePathname } from '@tinloof/sanity-studio' 2 | import { defineType } from 'sanity' 3 | import { StickyNote } from 'lucide-react' 4 | 5 | export default defineType({ 6 | type: 'document', 7 | name: 'page', 8 | fields: [ 9 | { 10 | type: 'string', 11 | name: 'title', 12 | }, 13 | { 14 | type: 'image', 15 | name: 'image', 16 | }, 17 | definePathname({ name: 'pathname' }), 18 | ], 19 | preview: { 20 | select: { 21 | title: 'title', 22 | image: 'image', 23 | }, 24 | prepare({ title, image }) { 25 | return { 26 | title, 27 | media: image, 28 | } 29 | }, 30 | }, 31 | }) 32 | -------------------------------------------------------------------------------- /examples/hello-world/styles/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | -webkit-font-smoothing: antialiased; 3 | -moz-osx-font-smoothing: grayscale; 4 | overflow-x: hidden; 5 | } 6 | 7 | p:not(:last-child) { 8 | margin-bottom: 0.875rem; 9 | } 10 | 11 | ol, 12 | ul { 13 | margin-left: 1rem; 14 | } 15 | 16 | ol { 17 | list-style-type: disc; 18 | } 19 | -------------------------------------------------------------------------------- /examples/hello-world/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | content: [ 6 | './app/**/*.{js,ts,jsx,tsx}', 7 | './components/**/*.{js,ts,jsx,tsx}', 8 | './intro-template/**/*.{js,ts,jsx,tsx}', 9 | ], 10 | theme: { 11 | ...defaultTheme, 12 | // Overriding fontFamily to use @next/font loaded families 13 | fontFamily: { 14 | mono: 'var(--font-mono)', 15 | sans: 'var(--font-sans)', 16 | serif: 'var(--font-serif)', 17 | }, 18 | }, 19 | plugins: [require('@tailwindcss/typography')], 20 | } 21 | -------------------------------------------------------------------------------- /examples/hello-world/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "strictNullChecks": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "paths": { 24 | "@/*": ["./*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /examples/hello-world/types/index.ts: -------------------------------------------------------------------------------- 1 | export type PagePayload = { 2 | _id: string 3 | _type: string 4 | pathname: string 5 | title?: string 6 | } 7 | -------------------------------------------------------------------------------- /examples/with-i18n/.env.example: -------------------------------------------------------------------------------- 1 | # Created by Vercel CLI 2 | NEXT_PUBLIC_SANITY_DATASET="production" 3 | NEXT_PUBLIC_SANITY_PROJECT_ID="" 4 | NEXT_PUBLIC_URL="http://localhost:3000" 5 | SANITY_API_TOKEN="" 6 | -------------------------------------------------------------------------------- /examples/with-i18n/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/with-i18n/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /studio/node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | /studio/dist 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | .vscode 24 | .idea/ 25 | *.iml 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | .env*.local 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | 42 | # Env files created by scripts for working locally 43 | .env 44 | studio/.env.development 45 | -------------------------------------------------------------------------------- /examples/with-i18n/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # with-i18n 2 | 3 | ## null 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [89b54ad] 8 | - Updated dependencies [454a00f] 9 | - @tinloof/sanity-studio@1.3.4 10 | 11 | ## null 12 | 13 | ### Patch Changes 14 | 15 | - Updated dependencies [8059797] 16 | - @tinloof/sanity-studio@1.3.3 17 | 18 | ## null 19 | 20 | ### Patch Changes 21 | 22 | - Updated dependencies [754535d] 23 | - Updated dependencies [55dae45] 24 | - Updated dependencies [928c529] 25 | - @tinloof/sanity-studio@1.3.2 26 | 27 | ## null 28 | 29 | ### Patch Changes 30 | 31 | - Updated dependencies [95a15b3] 32 | - Updated dependencies [9e62382] 33 | - @tinloof/sanity-studio@1.3.1 34 | 35 | ## null 36 | 37 | ### Patch Changes 38 | 39 | - Updated dependencies [2a263d7] 40 | - Updated dependencies [a09d24f] 41 | - Updated dependencies [29a848a] 42 | - Updated dependencies [4289934] 43 | - Updated dependencies [4c92cc8] 44 | - Updated dependencies [ac331cf] 45 | - Updated dependencies [7dd9046] 46 | - Updated dependencies [ab53988] 47 | - @tinloof/sanity-studio@1.3.0 48 | 49 | ## null 50 | 51 | ### Patch Changes 52 | 53 | - Updated dependencies [a3677bb] 54 | - @tinloof/sanity-studio@1.2.1 55 | 56 | ## null 57 | 58 | ### Patch Changes 59 | 60 | - Updated dependencies [08efb47] 61 | - Updated dependencies [e5aa2a8] 62 | - @tinloof/sanity-studio@1.2.0 63 | 64 | ## null 65 | 66 | ### Patch Changes 67 | 68 | - Updated dependencies [c96e9f7] 69 | - @tinloof/sanity-studio@1.1.3 70 | 71 | ## null 72 | 73 | ### Patch Changes 74 | 75 | - @tinloof/sanity-studio@1.1.2 76 | 77 | ## null 78 | 79 | ### Patch Changes 80 | 81 | - @tinloof/sanity-studio@1.1.1 82 | 83 | ## null 84 | 85 | ### Patch Changes 86 | 87 | - Updated dependencies [e48e6e5] 88 | - Updated dependencies [ec602d8] 89 | - @tinloof/sanity-studio@1.1.0 90 | 91 | ## null 92 | 93 | ### Patch Changes 94 | 95 | - Updated dependencies [5beeb0a] 96 | - @tinloof/sanity-studio@1.0.2 97 | 98 | ## null 99 | 100 | ### Patch Changes 101 | 102 | - Updated dependencies [b442ca5] 103 | - @tinloof/sanity-studio@1.0.1 104 | 105 | ## null 106 | 107 | ### Patch Changes 108 | 109 | - Updated dependencies [3899f1a] 110 | - @tinloof/sanity-studio@1.0.0 111 | 112 | ## null 113 | 114 | ### Patch Changes 115 | 116 | - Updated dependencies [a09953c] 117 | - @tinloof/sanity-studio@0.4.1 118 | -------------------------------------------------------------------------------- /examples/with-i18n/README.md: -------------------------------------------------------------------------------- 1 | # With i18n 2 | 3 | Minimalist template of a Next.js website managed from Sanity. 4 | 5 | ![Studio Overview](./public/static/studio.png) 6 | 7 | ## Running the project locally 8 | 9 | ``` 10 | npm run dev 11 | ``` 12 | 13 | ## Set up 14 | 15 | ### 1. Set up the environment 16 | 17 | Copy `.env.example` and fill it up with your Sanity project variables: 18 | 19 | ``` 20 | cp .env.example .env 21 | ``` 22 | 23 | Alternatively, if you're deploying on Vercel, run the following: 24 | 25 | ``` 26 | vercel link 27 | vercel env pull .env 28 | ``` 29 | 30 | ### 2. Install dependencies 31 | 32 | ``` 33 | npm i 34 | ``` 35 | -------------------------------------------------------------------------------- /examples/with-i18n/app/(website)/[locale]/[...path]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from '@/components/Page' 2 | import { loadPage } from '@/data/sanity' 3 | 4 | export default async function DynamicRoute({ 5 | params, 6 | }: { 7 | params: Promise<{ path: string[]; locale: string }> 8 | }) { 9 | const { path, locale } = await params 10 | const pathname = `/${path.join('/')}` 11 | const data = await loadPage(pathname, locale) 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /examples/with-i18n/app/(website)/[locale]/layout.tsx: -------------------------------------------------------------------------------- 1 | import 'tailwindcss/tailwind.css' 2 | 3 | import { Metadata } from 'next' 4 | import { VisualEditing } from 'next-sanity' 5 | import { revalidatePath, revalidateTag } from 'next/cache' 6 | import { Inter } from 'next/font/google' 7 | import { draftMode } from 'next/headers' 8 | 9 | import config from '@/config' 10 | 11 | export const metadata: Metadata = { 12 | title: `${config.siteName} - Website`, 13 | } 14 | 15 | const sans = Inter({ 16 | variable: '--font-sans', 17 | subsets: ['latin'], 18 | }) 19 | 20 | export default async function RootLayout({ 21 | params, 22 | children, 23 | }: { 24 | params: Promise<{ locale: string }> 25 | children: React.ReactNode 26 | }) { 27 | const locale = (await params).locale 28 | const isDraftModeEnabled = (await draftMode()).isEnabled 29 | 30 | return ( 31 | 32 | 33 | {children} 34 | {isDraftModeEnabled && ( 35 | { 37 | 'use server' 38 | if (!isDraftModeEnabled) { 39 | console.debug( 40 | 'Skipped manual refresh because draft mode is not enabled', 41 | ) 42 | return 43 | } 44 | if (payload.source === 'mutation') { 45 | if (payload.document.slug?.current) { 46 | const tag = `${payload.document._type}:${payload.document.slug.current}` 47 | console.log('Revalidate slug', tag) 48 | await revalidateTag(tag) 49 | } 50 | console.log('Revalidate tag', payload.document._type) 51 | return revalidateTag(payload.document._type) 52 | } 53 | await revalidatePath('/', 'layout') 54 | }} 55 | /> 56 | )} 57 | 58 | 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /examples/with-i18n/app/(website)/[locale]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from '@/components/Page' 2 | import { loadPage } from '@/data/sanity' 3 | 4 | export default async function IndexRoute({ 5 | params, 6 | }: { 7 | params: Promise<{ locale: string }> 8 | }) { 9 | const data = await loadPage('/', (await params).locale) 10 | 11 | return 12 | } 13 | -------------------------------------------------------------------------------- /examples/with-i18n/app/api/disable-draft/route.ts: -------------------------------------------------------------------------------- 1 | import { draftMode } from 'next/headers' 2 | import { NextRequest, NextResponse } from 'next/server' 3 | 4 | export async function GET(request: NextRequest) { 5 | ;(await draftMode()).disable() 6 | const url = new URL(request.nextUrl) 7 | return NextResponse.redirect(new URL('/', url.origin)) 8 | } 9 | -------------------------------------------------------------------------------- /examples/with-i18n/app/api/draft/route.ts: -------------------------------------------------------------------------------- 1 | import { validatePreviewUrl } from '@sanity/preview-url-secret' 2 | import { draftMode } from 'next/headers' 3 | import { redirect } from 'next/navigation' 4 | 5 | import config from '@/config' 6 | import { client } from '@/data/sanity/client' 7 | 8 | const clientWithToken = client.withConfig({ token: config.sanity.token }) 9 | 10 | export async function GET(request: Request) { 11 | const { isValid, redirectTo = '/' } = await validatePreviewUrl( 12 | clientWithToken, 13 | request.url, 14 | ) 15 | if (!isValid) { 16 | return new Response('Invalid secret', { status: 401 }) 17 | } 18 | 19 | ;(await draftMode()).enable() 20 | 21 | redirect(redirectTo) 22 | } 23 | -------------------------------------------------------------------------------- /examples/with-i18n/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/examples/with-i18n/app/favicon.ico -------------------------------------------------------------------------------- /examples/with-i18n/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/examples/with-i18n/app/icon.png -------------------------------------------------------------------------------- /examples/with-i18n/app/studio/[[...index]]/Studio.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { NextStudio } from 'next-sanity/studio' 4 | 5 | import config from '@/sanity.config' 6 | 7 | export default function Studio() { 8 | return 9 | } 10 | -------------------------------------------------------------------------------- /examples/with-i18n/app/studio/[[...index]]/page.tsx: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | import { Metadata } from 'next' 3 | import Studio from './Studio' 4 | 5 | export const dynamic = 'force-static' 6 | 7 | export const metadata: Metadata = { 8 | title: `${config.siteName} - CMS`, 9 | } 10 | 11 | export default function StudioPage() { 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /examples/with-i18n/app/studio/layout.tsx: -------------------------------------------------------------------------------- 1 | import 'tailwindcss/tailwind.css' 2 | 3 | export default async function RootLayout({ 4 | children, 5 | }: { 6 | children: React.ReactNode 7 | }) { 8 | return ( 9 | 10 | {children} 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /examples/with-i18n/components/Page.tsx: -------------------------------------------------------------------------------- 1 | import type { PagePayload } from '@/types' 2 | 3 | export interface PageProps { 4 | data: PagePayload | null 5 | } 6 | 7 | export function Page({ data }: PageProps) { 8 | const { title } = data ?? {} 9 | 10 | return ( 11 |
12 |

{title}

13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /examples/with-i18n/components/StudioLogo.tsx: -------------------------------------------------------------------------------- 1 | export default function StudioLogo() { 2 | return ( 3 | Tinloof Logo 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /examples/with-i18n/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | sanity: { 3 | projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || '', 4 | dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || '', 5 | // Not exposed to the front-end, used solely by the server 6 | token: process.env.SANITY_API_TOKEN || '', 7 | apiVersion: process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-06-21', 8 | revalidateSecret: process.env.SANITY_REVALIDATE_SECRET || '', 9 | studioUrl: '/studio', 10 | }, 11 | siteName: 'With i18n', 12 | siteDomain: process.env.NEXT_PUBLIC_SITE_DOMAIN || '', 13 | baseUrl: process.env.NEXT_PUBLIC_BASE_URL || '', 14 | i18n: { 15 | locales: [ 16 | { id: 'en', title: 'English' }, 17 | { id: 'fr', title: 'French' }, 18 | ], 19 | defaultLocaleId: 'en', 20 | }, 21 | } 22 | 23 | export default config 24 | -------------------------------------------------------------------------------- /examples/with-i18n/data/sanity/client.ts: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | import { ClientPerspective, createClient } from '@sanity/client' 3 | 4 | const clientConfig = { 5 | projectId: config.sanity.projectId, 6 | dataset: config.sanity.dataset, 7 | apiVersion: config.sanity.apiVersion, 8 | useCdn: process.env.NODE_ENV === 'production', 9 | perspective: 'published' as ClientPerspective, 10 | } 11 | 12 | export const client = createClient({ 13 | ...clientConfig, 14 | stega: { 15 | studioUrl: config.sanity.studioUrl, 16 | // logger: console, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /examples/with-i18n/data/sanity/generateStaticSlugs.ts: -------------------------------------------------------------------------------- 1 | import 'server-only' 2 | 3 | import { groq } from 'next-sanity' 4 | 5 | import config from '@/config' 6 | import { client } from '@/data/sanity/client' 7 | 8 | // Used in `generateStaticParams` 9 | export function generateStaticPaths(types: string[]) { 10 | return client 11 | .withConfig({ 12 | token: config.sanity.token, 13 | perspective: 'published', 14 | useCdn: false, 15 | stega: false, 16 | }) 17 | .fetch( 18 | groq`*[_type in $types && defined(pathname.current)][].pathname.current`, 19 | { types }, 20 | { 21 | next: { 22 | tags: types, 23 | }, 24 | }, 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /examples/with-i18n/data/sanity/index.ts: -------------------------------------------------------------------------------- 1 | import { PagePayload } from '@/types' 2 | import { loadQuery } from './loadQuery' 3 | import { PAGE_QUERY } from './queries' 4 | 5 | export async function loadPage(pathname: string, locale: string) { 6 | return loadQuery({ 7 | query: PAGE_QUERY, 8 | params: { pathname, locale }, 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /examples/with-i18n/data/sanity/loadQuery.ts: -------------------------------------------------------------------------------- 1 | import type { UnfilteredResponseQueryOptions } from '@sanity/client' 2 | import type { QueryParams } from 'next-sanity' 3 | 4 | import { draftMode } from 'next/headers' 5 | import 'server-only' 6 | 7 | import config from '@/config' 8 | import { client } from '@/data/sanity/client' 9 | 10 | const DEFAULT_PARAMS = {} as QueryParams 11 | 12 | export async function loadQuery({ 13 | query, 14 | params = DEFAULT_PARAMS, 15 | }: { 16 | query: string 17 | params?: QueryParams 18 | }): Promise { 19 | const isDraftMode = (await draftMode()).isEnabled 20 | const token = config.sanity.token 21 | 22 | if (isDraftMode && !token) { 23 | throw new Error( 24 | 'The `SANITY_API_READ_TOKEN` environment variable is required in Draft Mode.', 25 | ) 26 | } 27 | 28 | const perspective = isDraftMode ? 'previewDrafts' : 'published' 29 | 30 | const options = { 31 | filterResponse: false, 32 | useCdn: false, 33 | resultSourceMap: isDraftMode ? 'withKeyArraySelector' : false, 34 | token: isDraftMode ? token : undefined, 35 | perspective, 36 | next: { 37 | tags: ['sanity'], 38 | revalidate: isDraftMode ? 0 : undefined, 39 | }, 40 | } satisfies UnfilteredResponseQueryOptions 41 | const result = await client.fetch(query, params, { 42 | ...options, 43 | stega: isDraftMode, 44 | } as UnfilteredResponseQueryOptions) 45 | return result.result 46 | } 47 | -------------------------------------------------------------------------------- /examples/with-i18n/data/sanity/queries.ts: -------------------------------------------------------------------------------- 1 | import { groq } from 'next-sanity' 2 | 3 | export const PAGE_QUERY = groq`*[pathname.current == $pathname && locale == $locale][0]` 4 | -------------------------------------------------------------------------------- /examples/with-i18n/middleware.ts: -------------------------------------------------------------------------------- 1 | import appConfig from '@/config' 2 | import type { NextRequest } from 'next/server' 3 | import { NextResponse } from 'next/server' 4 | 5 | export async function middleware(request: NextRequest) { 6 | const pathname = request.nextUrl.pathname 7 | const searchParams = request.nextUrl.searchParams 8 | 9 | // Check if there is any supported locale in the pathname 10 | const pathnameIsMissingLocale = appConfig.i18n.locales.every( 11 | (locale) => 12 | !pathname.startsWith(`/${locale.id}/`) && pathname !== `/${locale.id}`, 13 | ) 14 | 15 | // Redirect if there is no locale 16 | if (pathnameIsMissingLocale) { 17 | const searchString = searchParams.toString() 18 | 19 | return NextResponse.rewrite( 20 | new URL( 21 | `/${appConfig.i18n.defaultLocaleId}${pathname}${searchString ? `?${searchString}` : ''}`, 22 | request.url, 23 | ), 24 | ) 25 | } 26 | } 27 | 28 | export const config = { 29 | matcher: ['/((?!api|_next/static|_next/image|favicon.ico|studio|static).*)'], 30 | } 31 | -------------------------------------------------------------------------------- /examples/with-i18n/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /examples/with-i18n/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const config = { 3 | images: { 4 | remotePatterns: [{ hostname: 'cdn.sanity.io' }], 5 | }, 6 | typescript: { 7 | // Set this to false if you want production builds to abort if there's type errors 8 | ignoreBuildErrors: process.env.VERCEL_ENV === 'production', 9 | }, 10 | eslint: { 11 | /// Set this to false if you want production builds to abort if there's lint errors 12 | ignoreDuringBuilds: process.env.VERCEL_ENV === 'production', 13 | }, 14 | logging: { 15 | fetches: { 16 | fullUrl: true, 17 | }, 18 | }, 19 | experimental: { 20 | taint: true, 21 | }, 22 | } 23 | 24 | export default config 25 | -------------------------------------------------------------------------------- /examples/with-i18n/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-i18n", 3 | "private": true, 4 | "scripts": { 5 | "build": "next build", 6 | "dev": "next --turbo", 7 | "format": "npx prettier --write . --ignore-path .gitignore", 8 | "lint": "next lint -- --ignore-path .gitignore", 9 | "lint:fix": "npm run format && npm run lint -- --fix", 10 | "start": "next start", 11 | "type-check": "tsc --noEmit" 12 | }, 13 | "prettier": { 14 | "semi": false, 15 | "singleQuote": true 16 | }, 17 | "dependencies": { 18 | "@sanity/client": "^6.27.2", 19 | "@sanity/preview-url-secret": "^2.1.4", 20 | "@sanity/vision": "^3.80.1", 21 | "@tailwindcss/typography": "0.5.15", 22 | "@tinloof/sanity-studio": "workspace:*", 23 | "classnames": "2.5.1", 24 | "lucide-react": "^0.453.0", 25 | "next": "15.2.3", 26 | "next-sanity": "^9.8.51", 27 | "react": "^18.3.1", 28 | "react-dom": "^18.3.1", 29 | "sanity": "^3.80.1", 30 | "server-only": "0.0.1" 31 | }, 32 | "devDependencies": { 33 | "@types/react": "^18.3.11", 34 | "@types/react-dom": "^18.3.1", 35 | "autoprefixer": "10.4.20", 36 | "eslint": "^8.57.0", 37 | "eslint-config-next": "15.1.1", 38 | "eslint-config-prettier": "^9.1.0", 39 | "eslint-plugin-prettier": "^5.2.1", 40 | "eslint-plugin-simple-import-sort": "12.1.1", 41 | "postcss": "8.4.47", 42 | "prettier": "^3.3.3", 43 | "prettier-plugin-packagejson": "^2.5.3", 44 | "prettier-plugin-tailwindcss": "0.6.8", 45 | "tailwindcss": "3.4.14", 46 | "typescript": "^5.6.3" 47 | }, 48 | "version": null 49 | } 50 | -------------------------------------------------------------------------------- /examples/with-i18n/postcss.config.js: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /examples/with-i18n/public/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/examples/with-i18n/public/static/logo.png -------------------------------------------------------------------------------- /examples/with-i18n/public/static/studio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/examples/with-i18n/public/static/studio.png -------------------------------------------------------------------------------- /examples/with-i18n/sanity.cli.ts: -------------------------------------------------------------------------------- 1 | import { loadEnvConfig } from '@next/env' 2 | import { defineCliConfig } from 'sanity/cli' 3 | 4 | const dev = process.env.NODE_ENV !== 'production' 5 | loadEnvConfig(__dirname, dev, { info: () => null, error: console.error }) 6 | 7 | // @TODO report top-level await bug 8 | // Using a dynamic import here as `loadEnvConfig` needs to run before this file is loaded 9 | // const { projectId, dataset } = await import('@/lib/sanity.api') 10 | const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID 11 | const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET 12 | 13 | export default defineCliConfig({ api: { projectId, dataset } }) 14 | -------------------------------------------------------------------------------- /examples/with-i18n/sanity.config.ts: -------------------------------------------------------------------------------- 1 | import schemas from '@/sanity/schemas' 2 | import { visionTool } from '@sanity/vision' 3 | import { documentI18n, pages } from '@tinloof/sanity-studio' 4 | import { defineConfig } from 'sanity' 5 | import { structureTool } from 'sanity/structure' 6 | import StudioLogo from './components/StudioLogo' 7 | import config from './config' 8 | 9 | export default defineConfig({ 10 | basePath: config.sanity.studioUrl, 11 | projectId: config.sanity.projectId, 12 | dataset: config.sanity.dataset, 13 | title: config.siteName, 14 | icon: StudioLogo, 15 | schema: { 16 | types: schemas, 17 | }, 18 | plugins: [ 19 | pages({ 20 | previewUrl: { 21 | previewMode: { 22 | enable: '/api/draft', 23 | }, 24 | }, 25 | creatablePages: ['page'], 26 | i18n: config.i18n, 27 | }), 28 | documentI18n({ ...config.i18n, schemas }), 29 | structureTool(), 30 | visionTool({ defaultApiVersion: config.sanity.apiVersion }), 31 | ], 32 | }) 33 | -------------------------------------------------------------------------------- /examples/with-i18n/sanity/schemas/index.ts: -------------------------------------------------------------------------------- 1 | import page from './page' 2 | 3 | const schemas = [page] 4 | 5 | export default schemas 6 | -------------------------------------------------------------------------------- /examples/with-i18n/sanity/schemas/page.ts: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | import { definePathname } from '@tinloof/sanity-studio' 3 | import { defineType } from 'sanity' 4 | 5 | export default defineType({ 6 | type: 'document', 7 | name: 'page', 8 | fields: [ 9 | { 10 | type: 'string', 11 | name: 'title', 12 | }, 13 | definePathname({ 14 | name: 'pathname', 15 | options: { 16 | i18n: { 17 | enabled: true, 18 | defaultLocaleId: config.i18n.defaultLocaleId, 19 | }, 20 | }, 21 | }), 22 | { 23 | type: 'string', 24 | name: 'locale', 25 | hidden: true, 26 | }, 27 | ], 28 | }) 29 | -------------------------------------------------------------------------------- /examples/with-i18n/styles/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | -webkit-font-smoothing: antialiased; 3 | -moz-osx-font-smoothing: grayscale; 4 | overflow-x: hidden; 5 | } 6 | 7 | p:not(:last-child) { 8 | margin-bottom: 0.875rem; 9 | } 10 | 11 | ol, 12 | ul { 13 | margin-left: 1rem; 14 | } 15 | 16 | ol { 17 | list-style-type: disc; 18 | } 19 | -------------------------------------------------------------------------------- /examples/with-i18n/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | content: [ 6 | './app/**/*.{js,ts,jsx,tsx}', 7 | './components/**/*.{js,ts,jsx,tsx}', 8 | './intro-template/**/*.{js,ts,jsx,tsx}', 9 | ], 10 | theme: { 11 | ...defaultTheme, 12 | // Overriding fontFamily to use @next/font loaded families 13 | fontFamily: { 14 | mono: 'var(--font-mono)', 15 | sans: 'var(--font-sans)', 16 | serif: 'var(--font-serif)', 17 | }, 18 | }, 19 | plugins: [require('@tailwindcss/typography')], 20 | } 21 | -------------------------------------------------------------------------------- /examples/with-i18n/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "strictNullChecks": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "paths": { 24 | "@/*": ["./*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /examples/with-i18n/types/index.ts: -------------------------------------------------------------------------------- 1 | export type PagePayload = { 2 | _id: string 3 | _type: string 4 | pathname: { current: string } 5 | title?: string 6 | } 7 | -------------------------------------------------------------------------------- /examples/with-sections/.env.example: -------------------------------------------------------------------------------- 1 | # Created by Vercel CLI 2 | NEXT_PUBLIC_SANITY_DATASET="production" 3 | NEXT_PUBLIC_SANITY_PROJECT_ID="" 4 | NEXT_PUBLIC_URL="http://localhost:3000" 5 | SANITY_API_TOKEN="" 6 | -------------------------------------------------------------------------------- /examples/with-sections/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/with-sections/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /studio/node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | /studio/dist 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | .vscode 24 | .idea/ 25 | *.iml 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | .env*.local 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | 42 | # Env files created by scripts for working locally 43 | .env 44 | studio/.env.development 45 | -------------------------------------------------------------------------------- /examples/with-sections/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # with-sections 2 | 3 | ## null 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [89b54ad] 8 | - Updated dependencies [454a00f] 9 | - @tinloof/sanity-studio@1.3.4 10 | 11 | ## null 12 | 13 | ### Patch Changes 14 | 15 | - Updated dependencies [8059797] 16 | - @tinloof/sanity-studio@1.3.3 17 | 18 | ## null 19 | 20 | ### Patch Changes 21 | 22 | - Updated dependencies [754535d] 23 | - Updated dependencies [55dae45] 24 | - Updated dependencies [928c529] 25 | - @tinloof/sanity-studio@1.3.2 26 | 27 | ## null 28 | 29 | ### Patch Changes 30 | 31 | - Updated dependencies [95a15b3] 32 | - Updated dependencies [9e62382] 33 | - @tinloof/sanity-studio@1.3.1 34 | 35 | ## null 36 | 37 | ### Patch Changes 38 | 39 | - Updated dependencies [2a263d7] 40 | - Updated dependencies [a09d24f] 41 | - Updated dependencies [29a848a] 42 | - Updated dependencies [4289934] 43 | - Updated dependencies [4c92cc8] 44 | - Updated dependencies [ac331cf] 45 | - Updated dependencies [7dd9046] 46 | - Updated dependencies [ab53988] 47 | - @tinloof/sanity-studio@1.3.0 48 | 49 | ## null 50 | 51 | ### Patch Changes 52 | 53 | - Updated dependencies [a3677bb] 54 | - @tinloof/sanity-studio@1.2.1 55 | 56 | ## null 57 | 58 | ### Patch Changes 59 | 60 | - Updated dependencies [08efb47] 61 | - Updated dependencies [e5aa2a8] 62 | - @tinloof/sanity-studio@1.2.0 63 | 64 | ## null 65 | 66 | ### Patch Changes 67 | 68 | - Updated dependencies [c96e9f7] 69 | - @tinloof/sanity-studio@1.1.3 70 | 71 | ## null 72 | 73 | ### Patch Changes 74 | 75 | - @tinloof/sanity-studio@1.1.2 76 | 77 | ## null 78 | 79 | ### Patch Changes 80 | 81 | - @tinloof/sanity-studio@1.1.1 82 | 83 | ## null 84 | 85 | ### Patch Changes 86 | 87 | - Updated dependencies [e48e6e5] 88 | - Updated dependencies [ec602d8] 89 | - @tinloof/sanity-studio@1.1.0 90 | 91 | ## null 92 | 93 | ### Patch Changes 94 | 95 | - Updated dependencies [5beeb0a] 96 | - @tinloof/sanity-studio@1.0.2 97 | 98 | ## null 99 | 100 | ### Patch Changes 101 | 102 | - Updated dependencies [b442ca5] 103 | - @tinloof/sanity-studio@1.0.1 104 | 105 | ## null 106 | 107 | ### Patch Changes 108 | 109 | - Updated dependencies [3899f1a] 110 | - @tinloof/sanity-studio@1.0.0 111 | 112 | ## null 113 | 114 | ### Patch Changes 115 | 116 | - Updated dependencies [a09953c] 117 | - @tinloof/sanity-studio@0.4.1 118 | -------------------------------------------------------------------------------- /examples/with-sections/README.md: -------------------------------------------------------------------------------- 1 | # With Sections 2 | 3 | Minimalist template of a Next.js website managed from Sanity. 4 | 5 | ![Studio Overview](./public/studio.png) 6 | 7 | ## Running the project locally 8 | 9 | ``` 10 | npm run dev 11 | ``` 12 | 13 | ## Set up 14 | 15 | ### 1. Set up the environment 16 | 17 | Copy `.env.example` and fill it up with your Sanity project variables: 18 | 19 | ``` 20 | cp .env.example .env 21 | ``` 22 | 23 | Alternatively, if you're deploying on Vercel, run the following: 24 | 25 | ``` 26 | vercel link 27 | vercel env pull .env 28 | ``` 29 | 30 | ### 2. Install dependencies 31 | 32 | ``` 33 | npm i 34 | ``` 35 | -------------------------------------------------------------------------------- /examples/with-sections/app/(website)/[...path]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from '@/components/Page' 2 | import { loadPage } from '@/data/sanity' 3 | 4 | export default async function DynamicRoute({ 5 | params, 6 | }: { 7 | params: Promise<{ path: string[] }> 8 | }) { 9 | const pathname = `/${(await params).path.join('/')}` 10 | const data = await loadPage(pathname) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /examples/with-sections/app/(website)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next' 2 | import { VisualEditing } from 'next-sanity' 3 | import { revalidatePath, revalidateTag } from 'next/cache' 4 | import { draftMode } from 'next/headers' 5 | 6 | import config from '@/config' 7 | 8 | export const metadata: Metadata = { 9 | title: `${config.siteName} - Website`, 10 | } 11 | 12 | export default async function Layout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | const isDraftModeEnabled = (await draftMode()).isEnabled 18 | return ( 19 | <> 20 | {children} 21 | {isDraftModeEnabled && ( 22 | { 24 | 'use server' 25 | if (!isDraftModeEnabled) { 26 | console.debug( 27 | 'Skipped manual refresh because draft mode is not enabled', 28 | ) 29 | return 30 | } 31 | if (payload.source === 'mutation') { 32 | if (payload.document.slug?.current) { 33 | const tag = `${payload.document._type}:${payload.document.slug.current}` 34 | console.log('Revalidate slug', tag) 35 | await revalidateTag(tag) 36 | } 37 | console.log('Revalidate tag', payload.document._type) 38 | return revalidateTag(payload.document._type) 39 | } 40 | await revalidatePath('/', 'layout') 41 | }} 42 | /> 43 | )} 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /examples/with-sections/app/(website)/page.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from '@/components/Page' 2 | import { loadPage } from '@/data/sanity' 3 | 4 | export default async function IndexRoute() { 5 | const data = await loadPage('/') 6 | 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /examples/with-sections/app/api/disable-draft/route.ts: -------------------------------------------------------------------------------- 1 | import { draftMode } from 'next/headers' 2 | import { NextRequest, NextResponse } from 'next/server' 3 | 4 | export async function GET(request: NextRequest) { 5 | ;(await draftMode()).disable() 6 | const url = new URL(request.nextUrl) 7 | return NextResponse.redirect(new URL('/', url.origin)) 8 | } 9 | -------------------------------------------------------------------------------- /examples/with-sections/app/api/draft/route.ts: -------------------------------------------------------------------------------- 1 | import { validatePreviewUrl } from '@sanity/preview-url-secret' 2 | import { draftMode } from 'next/headers' 3 | import { redirect } from 'next/navigation' 4 | 5 | import config from '@/config' 6 | import { client } from '@/data/sanity/client' 7 | 8 | const clientWithToken = client.withConfig({ token: config.sanity.token }) 9 | 10 | export async function GET(request: Request) { 11 | const { isValid, redirectTo = '/' } = await validatePreviewUrl( 12 | clientWithToken, 13 | request.url, 14 | ) 15 | if (!isValid) { 16 | return new Response('Invalid secret', { status: 401 }) 17 | } 18 | 19 | ;(await draftMode()).enable() 20 | 21 | redirect(redirectTo) 22 | } 23 | -------------------------------------------------------------------------------- /examples/with-sections/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/examples/with-sections/app/favicon.ico -------------------------------------------------------------------------------- /examples/with-sections/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinloof/sanity-kit/00fe748bb159f64b8d4b30b1c22c6aab891fe99a/examples/with-sections/app/icon.png -------------------------------------------------------------------------------- /examples/with-sections/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import 'tailwindcss/tailwind.css' 2 | 3 | import { Inter } from 'next/font/google' 4 | 5 | const sans = Inter({ 6 | variable: '--font-sans', 7 | subsets: ['latin'], 8 | }) 9 | 10 | export default async function RootLayout({ 11 | children, 12 | }: { 13 | children: React.ReactNode 14 | }) { 15 | return ( 16 | 17 | {children} 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /examples/with-sections/app/studio/[[...index]]/Studio.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { NextStudio } from 'next-sanity/studio' 4 | 5 | import config from '@/sanity.config' 6 | 7 | export default function Studio() { 8 | return 9 | } 10 | -------------------------------------------------------------------------------- /examples/with-sections/app/studio/[[...index]]/page.tsx: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | import { Metadata } from 'next' 3 | import Studio from './Studio' 4 | 5 | export const dynamic = 'force-static' 6 | 7 | export const metadata: Metadata = { 8 | title: `${config.siteName} - CMS`, 9 | } 10 | 11 | export default function StudioPage() { 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /examples/with-sections/components/Page.tsx: -------------------------------------------------------------------------------- 1 | import type { PagePayload } from '@/types' 2 | import { SectionRenderer } from './sections' 3 | 4 | export interface PageProps { 5 | data: PagePayload | null 6 | } 7 | 8 | export function Page({ data }: PageProps) { 9 | return ( 10 |
11 | {data?.sectionsBody?.map((section) => { 12 | return 13 | })} 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /examples/with-sections/components/StudioLogo.tsx: -------------------------------------------------------------------------------- 1 | export default function StudioLogo() { 2 | return ( 3 | Tinloof Logo 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /examples/with-sections/components/sections/Header.tsx: -------------------------------------------------------------------------------- 1 | export function Header(props: { title: string }) { 2 | return ( 3 |
4 | 9 |