├── studio ├── .prettierignore ├── static │ └── .gitkeep ├── .eslintrc ├── schemas │ ├── index.ts │ ├── post.ts │ └── blockContent.ts ├── .env.example ├── README.md ├── .gitignore ├── tsconfig.json ├── sanity.cli.ts ├── sanity.config.ts └── package.json ├── sveltekit-app ├── .npmrc ├── postcss.config.cjs ├── static │ ├── favicon.png │ ├── sanity.svg │ ├── global.css │ └── svelte.svg ├── vite.config.ts ├── .gitignore ├── src │ ├── lib │ │ ├── utils │ │ │ └── index.ts │ │ ├── server │ │ │ └── sanity │ │ │ │ ├── api.ts │ │ │ │ └── client.ts │ │ └── sanity │ │ │ ├── image.ts │ │ │ ├── client.ts │ │ │ ├── queries.ts │ │ │ └── api.ts │ ├── components │ │ ├── LiveMode.svelte │ │ ├── Card.svelte │ │ └── Welcome.svelte │ ├── app.d.ts │ ├── routes │ │ ├── +layout.server.ts │ │ ├── +layout.ts │ │ ├── +page.svelte │ │ ├── +page.server.ts │ │ ├── post │ │ │ └── [slug] │ │ │ │ ├── +page.server.ts │ │ │ │ └── +page.svelte │ │ └── +layout.svelte │ ├── hooks.server.ts │ └── app.html ├── .eslintignore ├── .prettierignore ├── .prettierrc ├── .env.example ├── .eslintrc.cjs ├── tsconfig.json ├── svelte.config.js ├── README.md └── package.json ├── .github ├── CODEOWNERS ├── renovate.json └── workflows │ └── validate.yml ├── pnpm-workspace.yaml ├── .gitignore ├── package.json └── README.md /studio/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /sveltekit-app/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @sanity-io/ecosystem 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'sveltekit-app' 3 | - 'studio' 4 | -------------------------------------------------------------------------------- /sveltekit-app/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('autoprefixer')] 3 | }; 4 | -------------------------------------------------------------------------------- /studio/static/.gitkeep: -------------------------------------------------------------------------------- 1 | Files placed here will be served by the Sanity server under the `/static`-prefix 2 | -------------------------------------------------------------------------------- /studio/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@sanity/eslint-config-studio", 4 | "@babel/core" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /sveltekit-app/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/sanity-template-sveltekit-clean/HEAD/sveltekit-app/static/favicon.png -------------------------------------------------------------------------------- /studio/schemas/index.ts: -------------------------------------------------------------------------------- 1 | import blockContent from './blockContent' 2 | import post from './post' 3 | 4 | export const schemaTypes = [post, blockContent] 5 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>sanity-io/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /sveltekit-app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /sveltekit-app/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /sveltekit-app/src/lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function formatDate(date: string) { 2 | return new Date(date).toLocaleDateString('en-US', { 3 | month: 'long', 4 | day: 'numeric', 5 | year: 'numeric' 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /sveltekit-app/.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /sveltekit-app/src/lib/server/sanity/api.ts: -------------------------------------------------------------------------------- 1 | import { SANITY_API_READ_TOKEN } from '$env/static/private'; 2 | import { assertEnvVar } from '$lib/sanity/api'; 3 | 4 | export const token = assertEnvVar(SANITY_API_READ_TOKEN, 'SANITY_API_READ_TOKEN'); 5 | -------------------------------------------------------------------------------- /sveltekit-app/.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /sveltekit-app/src/lib/server/sanity/client.ts: -------------------------------------------------------------------------------- 1 | import { client } from '$lib/sanity/client'; 2 | import { token } from '$lib/server/sanity/api'; 3 | 4 | export const serverClient = client.withConfig({ 5 | token, 6 | useCdn: false, 7 | stega: true 8 | }); 9 | -------------------------------------------------------------------------------- /sveltekit-app/src/components/LiveMode.svelte: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /sveltekit-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /sveltekit-app/src/lib/sanity/image.ts: -------------------------------------------------------------------------------- 1 | import imageUrlBuilder from '@sanity/image-url'; 2 | import type { Image } from '@sanity/types'; 3 | import { client } from './client'; 4 | 5 | const builder = imageUrlBuilder(client); 6 | 7 | export function urlFor(source: Image) { 8 | return builder.image(source); 9 | } 10 | -------------------------------------------------------------------------------- /sveltekit-app/src/lib/sanity/client.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@sanity/client'; 2 | import { apiVersion, projectId, dataset, studioUrl } from '$lib/sanity/api'; 3 | 4 | export const client = createClient({ 5 | projectId, 6 | dataset, 7 | apiVersion, 8 | useCdn: true, 9 | stega: { 10 | studioUrl 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: Validate Template 2 | on: push 3 | 4 | jobs: 5 | validate: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - name: Validate Sanity Template 10 | uses: sanity-io/template-validator@v0.1.6 11 | with: 12 | repository: ${{ github.repository }} 13 | -------------------------------------------------------------------------------- /studio/.env.example: -------------------------------------------------------------------------------- 1 | # Required - The ID of your Sanity project 2 | SANITY_STUDIO_PROJECT_ID="" 3 | # Required - The dataset of your Sanity project 4 | SANITY_STUDIO_DATASET="" 5 | # Optional - The URL of your application, for use in Presentation 6 | SANITY_STUDIO_PREVIEW_URL="" 7 | # Optional - The URL to deploy your Sanity Studio to, 8 | SANITY_STUDIO_STUDIO_HOST="" 9 | -------------------------------------------------------------------------------- /sveltekit-app/src/app.d.ts: -------------------------------------------------------------------------------- 1 | import type { LoaderLocals } from '@sanity/svelte-loader'; 2 | 3 | // See https://kit.svelte.dev/docs/types#app 4 | // for information about these interfaces 5 | declare global { 6 | namespace App { 7 | // interface Error {} 8 | interface Locals extends LoaderLocals {} 9 | // interface PageData {} 10 | // interface Platform {} 11 | } 12 | } 13 | 14 | export {}; 15 | -------------------------------------------------------------------------------- /sveltekit-app/.env.example: -------------------------------------------------------------------------------- 1 | # Required - The ID of your Sanity project 2 | PUBLIC_SANITY_PROJECT_ID="" 3 | # Required - The dataset of your Sanity project 4 | PUBLIC_SANITY_DATASET="" 5 | # Required - The generated read token, used to fetch data on the server 6 | SANITY_API_READ_TOKEN="" 7 | # Optional - The Sanity API version to use 8 | PUBLIC_SANITY_API_VERSION="" 9 | # Optional - The URL of your Sanity Studio 10 | PUBLIC_SANITY_STUDIO_URL="" 11 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # misc 10 | .DS_Store 11 | *.pem 12 | 13 | # debug 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # env files 19 | .env 20 | .env.* 21 | !.env.example 22 | 23 | # vercel 24 | .vercel 25 | 26 | # typescript 27 | *.tsbuildinfo 28 | next-env.d.ts -------------------------------------------------------------------------------- /sveltekit-app/src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutServerLoad } from './$types'; 2 | 3 | export const load: LayoutServerLoad = (event) => { 4 | // The `event.locals.preview` value received here is set by the helper function 5 | // in `hooks.server.ts`. It indicates whether the app is in preview mode or not. 6 | const { preview } = event.locals; 7 | // As `event.locals` is only available on the server, we can expose the value 8 | // to the client by returning it here. 9 | return { preview }; 10 | }; 11 | -------------------------------------------------------------------------------- /sveltekit-app/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | import { setPreviewing } from '@sanity/visual-editing/svelte'; 2 | import type { LayoutLoad } from './$types'; 3 | 4 | export const load: LayoutLoad = (event) => { 5 | // The `event.data.preview` value received here is exposed by the 6 | // corresponding `+layout.server.ts` file. 7 | const { preview } = event.data; 8 | // `@sanity/visual-editing/svelte` exports two helpers for setting and getting 9 | // preview state on the client: `setPreviewing` and `isPreviewing`. 10 | setPreviewing(preview); 11 | }; 12 | -------------------------------------------------------------------------------- /studio/README.md: -------------------------------------------------------------------------------- 1 | # Sanity Blogging 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 | -------------------------------------------------------------------------------- /sveltekit-app/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | {#if posts.length} 15 | {#each posts as post} 16 | 17 | {/each} 18 | {:else} 19 | 20 | {/if} 21 |
22 | -------------------------------------------------------------------------------- /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 | .env 8 | 9 | # Compiled Sanity Studio 10 | /dist 11 | 12 | # Temporary Sanity runtime, generated by the CLI on every dev server start 13 | /.sanity 14 | 15 | # Logs 16 | /logs 17 | *.log 18 | 19 | # Coverage directory used by testing tools 20 | /coverage 21 | 22 | # Misc 23 | .DS_Store 24 | *.pem 25 | 26 | # Typescript 27 | *.tsbuildinfo 28 | 29 | # Dotenv and similar local-only files 30 | *.local 31 | -------------------------------------------------------------------------------- /sveltekit-app/src/routes/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { postsQuery as query, type Post } from '$lib/sanity/queries'; 2 | import type { PageServerLoad } from './$types'; 3 | 4 | export const load: PageServerLoad = async (event) => { 5 | const { loadQuery } = event.locals; 6 | const initial = await loadQuery(query); 7 | 8 | // We pass the data in a format that is easy for `useQuery` to consume in the 9 | // corresponding `+page.svelte` file, but you can return the data in any 10 | // format you like. 11 | return { 12 | query, 13 | options: { initial } 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /sveltekit-app/src/lib/sanity/queries.ts: -------------------------------------------------------------------------------- 1 | import type { PortableTextBlock } from '@portabletext/types'; 2 | import type { ImageAsset, Slug } from '@sanity/types'; 3 | import groq from 'groq'; 4 | 5 | export const postQuery = groq`*[_type == "post" && slug.current == $slug][0]`; 6 | 7 | export const postsQuery = groq`*[_type == "post" && defined(slug.current)] | order(_createdAt desc)`; 8 | 9 | export interface Post { 10 | _type: 'post'; 11 | _createdAt: string; 12 | title?: string; 13 | slug: Slug; 14 | excerpt?: string; 15 | mainImage?: ImageAsset; 16 | body: PortableTextBlock[]; 17 | } 18 | -------------------------------------------------------------------------------- /sveltekit-app/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /sveltekit-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sanity-template-sveltekit-clean", 3 | "version": "1.0.0", 4 | "private": true, 5 | "workspaces": [ 6 | "sveltekit-app", 7 | "studio" 8 | ], 9 | "scripts": { 10 | "dev": "concurrently \"npm run dev --workspace=sveltekit-app\" \"npm run dev --workspace=studio\"", 11 | "format": "prettier --cache --write ." 12 | }, 13 | "prettier": { 14 | "plugins": [ 15 | "prettier-plugin-packagejson" 16 | ], 17 | "semi": false, 18 | "singleQuote": true 19 | }, 20 | "devDependencies": { 21 | "concurrently": "^9.1.0", 22 | "prettier": "3.3.3", 23 | "prettier-plugin-packagejson": "2.5.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sveltekit-app/src/routes/post/[slug]/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { postQuery as query, type Post } from '$lib/sanity/queries'; 2 | import type { PageServerLoad } from './$types'; 3 | 4 | export const load: PageServerLoad = async (event) => { 5 | const { loadQuery } = event.locals; 6 | const { slug } = event.params; 7 | 8 | const params = { slug }; 9 | const initial = await loadQuery(query, params); 10 | 11 | // We pass the data in a format that is easy for `useQuery` to consume in the 12 | // corresponding `+page.svelte` file, but you can return the data in any 13 | // format you like. 14 | return { 15 | query, 16 | params, 17 | options: { initial } 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /studio/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": false, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true 21 | }, 22 | "includes": [ 23 | "**/*.ts", 24 | "**/*.tsx" 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "dist" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /sveltekit-app/src/lib/sanity/api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PUBLIC_SANITY_DATASET, 3 | PUBLIC_SANITY_PROJECT_ID, 4 | PUBLIC_SANITY_API_VERSION, 5 | PUBLIC_SANITY_STUDIO_URL 6 | } from '$env/static/public'; 7 | 8 | export function assertEnvVar(value: T | undefined, name: string): T { 9 | if (value === undefined) { 10 | throw new Error(`Missing environment variable: ${name}`); 11 | } 12 | return value; 13 | } 14 | 15 | export const dataset = assertEnvVar(PUBLIC_SANITY_DATASET, 'PUBLIC_SANITY_DATASET'); 16 | 17 | export const projectId = assertEnvVar(PUBLIC_SANITY_PROJECT_ID, 'PUBLIC_SANITY_PROJECT_ID'); 18 | 19 | export const apiVersion = PUBLIC_SANITY_API_VERSION || '2024-03-15'; 20 | 21 | export const studioUrl = PUBLIC_SANITY_STUDIO_URL || 'http://localhost:3333'; 22 | -------------------------------------------------------------------------------- /sveltekit-app/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter() 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /sveltekit-app/src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import { createRequestHandler, setServerClient } from '@sanity/svelte-loader'; 2 | import { serverClient } from '$lib/server/sanity/client'; 3 | 4 | // Sets the client to be used by `loadQuery` when fetching data on the server. 5 | // The loader will handle setting the correct fetch parameters, including 6 | // perspective. This isn't a hook, but it's a good place to call this function 7 | // as this file is executed once per app initialization. 8 | setServerClient(serverClient); 9 | 10 | // This convenience function sets up preview mode endpoints and attaches useful 11 | // helpers to the `event.locals` Svelte object, such as a preconfigured 12 | // `loadQuery` function and `preview` state. 13 | export const handle = createRequestHandler(); 14 | -------------------------------------------------------------------------------- /studio/sanity.cli.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sanity CLI Configuration 3 | * This file configures the Sanity CLI tool with project-specific settings 4 | * and customizes the Vite bundler configuration. 5 | * Learn more: https://www.sanity.io/docs/cli 6 | */ 7 | 8 | import {defineCliConfig} from 'sanity/cli' 9 | 10 | const projectId = process.env.SANITY_STUDIO_PROJECT_ID || '' 11 | const dataset = process.env.SANITY_STUDIO_DATASET || 'production' 12 | 13 | export default defineCliConfig({ 14 | api: { 15 | projectId, 16 | dataset, 17 | }, 18 | studioHost: process.env.SANITY_STUDIO_STUDIO_HOST || '', // Visit https://www.sanity.io/docs/environment-variables to leanr more about using environment variables for local & production. 19 | autoUpdates: true, 20 | }) 21 | -------------------------------------------------------------------------------- /sveltekit-app/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | %sveltekit.head% 18 | 19 | 20 |
%sveltekit.body%
21 | 22 | 23 | -------------------------------------------------------------------------------- /studio/sanity.config.ts: -------------------------------------------------------------------------------- 1 | import {visionTool} from '@sanity/vision' 2 | import {defineConfig} from 'sanity' 3 | import {structureTool} from 'sanity/structure' 4 | import {presentationTool} from 'sanity/presentation' 5 | 6 | import {schemaTypes} from './schemas' 7 | 8 | const projectId = process.env.SANITY_STUDIO_PROJECT_ID! 9 | const dataset = process.env.SANITY_STUDIO_DATASET! 10 | 11 | export default defineConfig({ 12 | name: 'sanity-template-sveltekit-clean', 13 | title: 'Clean SvelteKit + Sanity app', 14 | projectId, 15 | dataset, 16 | plugins: [ 17 | structureTool(), 18 | presentationTool({ 19 | previewUrl: { 20 | origin: process.env.SANITY_STUDIO_PREVIEW_URL || 'http://localhost:5173', 21 | previewMode: { 22 | enable: '/preview/enable', 23 | disable: '/preview/disable', 24 | }, 25 | }, 26 | }), 27 | visionTool(), 28 | ], 29 | schema: { 30 | types: schemaTypes, 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /studio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "studio", 3 | "private": true, 4 | "version": "1.0.0", 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.66.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-is": "^18.2.0", 22 | "sanity": "^3.66.0", 23 | "styled-components": "^6.1.8" 24 | }, 25 | "devDependencies": { 26 | "@sanity/eslint-config-studio": "^3.0.1", 27 | "@types/node": "^18.19.23", 28 | "@types/react": "^18.3.3", 29 | "eslint": "^8.57.0", 30 | "prettier": "^3.3.3", 31 | "typescript": "^5.5.3" 32 | }, 33 | "prettier": { 34 | "semi": false, 35 | "printWidth": 100, 36 | "bracketSpacing": false, 37 | "singleQuote": true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sveltekit-app/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /studio/schemas/post.ts: -------------------------------------------------------------------------------- 1 | import {defineField, defineType} from 'sanity' 2 | 3 | export default defineType({ 4 | name: 'post', 5 | title: 'Post', 6 | type: 'document', 7 | fields: [ 8 | defineField({ 9 | name: 'title', 10 | title: 'Title', 11 | type: 'string', 12 | }), 13 | defineField({ 14 | name: 'slug', 15 | title: 'Slug', 16 | type: 'slug', 17 | validation: (Rule) => Rule.required(), 18 | options: { 19 | source: 'title', 20 | maxLength: 96, 21 | }, 22 | }), 23 | defineField({ 24 | name: 'excerpt', 25 | title: 'Excerpt', 26 | type: 'text', 27 | rows: 4, 28 | }), 29 | defineField({ 30 | name: 'mainImage', 31 | title: 'Main image', 32 | type: 'image', 33 | options: { 34 | hotspot: true, 35 | }, 36 | }), 37 | defineField({ 38 | name: 'body', 39 | title: 'Body', 40 | type: 'blockContent', 41 | }), 42 | ], 43 | preview: { 44 | select: { 45 | title: 'title', 46 | author: 'author.name', 47 | media: 'mainImage', 48 | }, 49 | prepare(selection) { 50 | const {author} = selection 51 | return {...selection, subtitle: author && `by ${author}`} 52 | }, 53 | }, 54 | }) 55 | -------------------------------------------------------------------------------- /sveltekit-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltekit-app", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 10 | "format": "prettier --plugin-search-dir . --write ." 11 | }, 12 | "dependencies": { 13 | "@sanity/client": "^6.15.4", 14 | "@sanity/image-url": "^1.0.2", 15 | "@sanity/svelte-loader": "^1.11.1", 16 | "@sanity/visual-editing": "^2.0.0", 17 | "groq": "^3.33.0" 18 | }, 19 | "devDependencies": { 20 | "@portabletext/svelte": "^2.1.11", 21 | "@portabletext/types": "^2.0.13", 22 | "@sanity/types": "^3.52.4", 23 | "@sveltejs/adapter-auto": "^3.2.2", 24 | "@sveltejs/kit": "^2.9.0", 25 | "@sveltejs/vite-plugin-svelte": "^3.1.1", 26 | "@typescript-eslint/eslint-plugin": "^6.21.0", 27 | "@typescript-eslint/parser": "^6.21.0", 28 | "autoprefixer": "^10.4.20", 29 | "eslint": "^8.57.0", 30 | "eslint-config-prettier": "^9.1.0", 31 | "eslint-plugin-svelte": "^2.43.0", 32 | "postcss": "^8.4.41", 33 | "prettier": "^3.3.3", 34 | "prettier-plugin-svelte": "^3.2.6", 35 | "svelte": "^4.2.18", 36 | "tslib": "^2.6.3", 37 | "typescript": "^5.5.3", 38 | "vite": "^5.3.5" 39 | }, 40 | "type": "module" 41 | } 42 | -------------------------------------------------------------------------------- /sveltekit-app/static/sanity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sveltekit-app/static/global.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --space-0: 0; 3 | --space-1: 4px; 4 | --space-2: 8px; 5 | --space-3: 12px; 6 | --space-4: 20px; 7 | --space-5: 32px; 8 | --space-6: 52px; 9 | --space-7: 84px; 10 | --space-8: 136px; 11 | --space-9: 220px; 12 | 13 | --font-family-sans: Inter; 14 | --font-family-serif: PT Serif; 15 | --font-family-mono: IBM Plex Mono; 16 | 17 | --font-size-0: 12px; 18 | --font-size-1: 14px; 19 | --font-size-2: 16px; 20 | --font-size-3: 18px; 21 | --font-size-4: 20px; 22 | --font-size-5: 24px; 23 | --font-size-6: 30px; 24 | --font-size-7: 36px; 25 | --font-size-8: 48px; 26 | --font-size-9: 60px; 27 | --font-size-10: 72px; 28 | 29 | --line-height-0: 16px; 30 | --line-height-1: 20px; 31 | --line-height-2: 24px; 32 | --line-height-3: 28px; 33 | --line-height-4: 28px; 34 | --line-height-5: 32px; 35 | --line-height-6: 36px; 36 | --line-height-7: 40px; 37 | --line-height-8: 48px; 38 | --line-height-9: 60px; 39 | --line-height-10: 72px; 40 | --line-height-11: 96px; 41 | --line-height-12: 128px; 42 | 43 | --white: #fff; 44 | --black: #101112; 45 | --gray-200: #ced2d9; 46 | --gray-600: #6e7683; 47 | --blue-600: #1e61cd; 48 | --magenta-100: #f9d7eb; 49 | 50 | --max-width-0: 320px; 51 | --max-width-1: 768px; 52 | } 53 | 54 | html { 55 | background-color: var(--white); 56 | font-family: var(--font-family-sans), var(--font-family-serif), sans-serif; 57 | -webkit-text-size-adjust: 100%; 58 | -moz-text-size-adjust: 100%; 59 | text-size-adjust: 100%; 60 | } 61 | 62 | body { 63 | margin: 0; 64 | } 65 | -------------------------------------------------------------------------------- /studio/schemas/blockContent.ts: -------------------------------------------------------------------------------- 1 | import {defineType, defineArrayMember} from 'sanity' 2 | 3 | /** 4 | * This is the schema definition for the rich text fields used for 5 | * for this blog studio. When you import it in schemas.js it can be 6 | * reused in other parts of the studio with: 7 | * { 8 | * name: 'someName', 9 | * title: 'Some title', 10 | * type: 'blockContent' 11 | * } 12 | */ 13 | export default defineType({ 14 | title: 'Block Content', 15 | name: 'blockContent', 16 | type: 'array', 17 | of: [ 18 | defineArrayMember({ 19 | title: 'Block', 20 | type: 'block', 21 | // Styles let you set what your user can mark up blocks with. These 22 | // correspond with HTML tags, but you can set any title or value 23 | // you want and decide how you want to deal with it where you want to 24 | // use your content. 25 | styles: [ 26 | {title: 'Normal', value: 'normal'}, 27 | {title: 'H1', value: 'h1'}, 28 | {title: 'H2', value: 'h2'}, 29 | {title: 'H3', value: 'h3'}, 30 | {title: 'H4', value: 'h4'}, 31 | {title: 'Quote', value: 'blockquote'}, 32 | ], 33 | lists: [{title: 'Bullet', value: 'bullet'}], 34 | // Marks let you mark up inline text in the block editor. 35 | marks: { 36 | // Decorators usually describe a single property – e.g. a typographic 37 | // preference or highlighting by editors. 38 | decorators: [ 39 | {title: 'Strong', value: 'strong'}, 40 | {title: 'Emphasis', value: 'em'}, 41 | ], 42 | // Annotations can be any object structure – e.g. a link or a footnote. 43 | annotations: [ 44 | { 45 | title: 'URL', 46 | name: 'link', 47 | type: 'object', 48 | fields: [ 49 | { 50 | title: 'URL', 51 | name: 'href', 52 | type: 'url', 53 | }, 54 | ], 55 | }, 56 | ], 57 | }, 58 | }), 59 | ], 60 | }) 61 | -------------------------------------------------------------------------------- /sveltekit-app/static/svelte.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sveltekit-app/src/components/Card.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | {#if post.mainImage} 11 | Cover image for {post.title} 16 | {:else} 17 |
18 | {/if} 19 | 20 |
21 |

22 | {post.title} 23 |

24 | {#if post.excerpt} 25 |

{post.excerpt}

26 | {/if} 27 |

28 | {formatDate(post._createdAt)} 29 |

30 |
31 |
32 | 33 | 133 | -------------------------------------------------------------------------------- /sveltekit-app/src/components/Welcome.svelte: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Svelte Logo 5 | + 6 | Sanity Logo 7 |
8 |
9 |

Next steps

10 |
    11 |
  • 12 |

    Publish a post in your Studio

    13 |

    Visit the Sanity Studio and publish a new document of type post.

    14 |
  • 15 |
  • 16 |

    Dive into the documentation

    17 |

    18 | Check out{' '} 19 | 20 | the documentation 21 | {' '} 22 | to learn more about Sanity. 23 |

    24 |
  • 25 |
  • 26 |

    Join the Sanity Community

    27 |

    28 | Leverage{' '} 29 | 34 | our awesome community 35 | 36 | , and share tips and discuss! 37 |

    38 |
  • 39 |
40 |
41 |
42 | 43 | 139 | -------------------------------------------------------------------------------- /sveltekit-app/src/routes/post/[slug]/+page.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | {#if post.mainImage} 16 | Cover image for {post.title} 21 | {:else} 22 |
23 | {/if} 24 |
25 |

{post.title}

26 | {#if post.excerpt} 27 |

{post.excerpt}

28 | {/if} 29 | 32 | {#if post.body} 33 |
34 | 35 |
36 | {/if} 37 |
38 |
39 | 40 | 139 | -------------------------------------------------------------------------------- /sveltekit-app/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | {#if $isPreviewing} 8 | 9 | Preview Enabled 10 | Disable Preview 11 | 12 | {/if} 13 | 14 |
15 |
16 | SvelteKit + Sanity 17 |
18 |
19 | 20 |
21 |
22 | 38 |
39 |
40 | 41 | {#if $isPreviewing} 42 | 43 | 44 | {/if} 45 | 46 | 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean SvelteKit + Sanity app 2 | 3 | This template includes a [SvelteKit](https://svelte.dev/docs/kit/introduction) app with a [Sanity Studio](https://www.sanity.io/) – an open-source React application that connects to your Sanity project’s hosted dataset. The Studio is configured locally and can then be deployed for content collaboration. 4 | 5 | ## Features 6 | 7 | - Fetch content seamlessly with [Sanity Content Lake](https://www.sanity.io/docs/datastore). 8 | - Render beautiful block content using [Portable Text](https://www.sanity.io/docs/presenting-block-text). 9 | - Manage and create content with the intuitive [Sanity Studio](https://www.sanity.io/docs/sanity-studio). 10 | - Live visual editing through [Sanity's Presentation tools](https://www.sanity.io/docs/presentation). 11 | - Advanced image cropping and rendering via [Sanity Image URLs](https://www.sanity.io/docs/image-url). 12 | 13 | ## Demo 14 | 15 | https://sanity-template-sveltekit-clean.sanity.build 16 | 17 | ## Getting Started 18 | 19 | ### Install the template 20 | 21 | #### 1. Initialize template with Sanity CLI 22 | 23 | Run the command in your Terminal to initialize this template on your local computer. 24 | 25 | See the documentation if you are [having issues with the CLI](https://www.sanity.io/help/cli-errors). 26 | 27 | ```shell 28 | npm create sanity@latest -- --template sanity-io/sanity-template-sveltekit-clean 29 | ``` 30 | 31 | #### 2. Run the application and Sanity Studio 32 | 33 | Navigate to the template directory using `cd `, and start the development servers by running the following command 34 | 35 | ```shell 36 | npm run dev 37 | ``` 38 | 39 | #### 3. Open the app and sign in to the Studio 40 | 41 | Open the SvelteKit app running locally in your browser on [http://localhost:5173](http://localhost:5173). 42 | 43 | Open the Studio running locally in your browser on [http://localhost:3333](http://localhost:3333). You should now see a screen prompting you to log in to the Studio. Use the same service (Google, GitHub, or email) that you used when you logged in to the CLI. 44 | 45 | ### Adding content with Sanity 46 | 47 | #### 1. Publish your first document 48 | 49 | The template comes pre-defined with a schema containing a `Post` document type. 50 | 51 | From the Studio, click "+ Create" and select the `Post` document type. Go ahead and create and publish the document. 52 | 53 | Your content should now appear in your SvelteKit app ([http://localhost:5173](http://localhost:5173)) as well as in the Studio on the "Presentation" Tab 54 | 55 | #### 2. Extending the Sanity schema 56 | 57 | The schema for the `Post` document type is defined in the `studio/src/schemaTypes/post.ts` file. You can [add more document types](https://www.sanity.io/docs/schema-types) to the schema to suit your needs. 58 | 59 | ### Deploying your application and inviting editors 60 | 61 | #### 1. Deploy Sanity Studio 62 | 63 | Your SvelteKit frontend (`/sveltekit-app`) and Sanity Studio (`/studio`) are still only running on your local computer. It's time to deploy and get it into the hands of other content editors. 64 | 65 | Back in your Studio directory (`/studio`), run the following command to deploy your Sanity Studio. 66 | 67 | ```shell 68 | npx sanity deploy 69 | ``` 70 | 71 | #### 2. Deploy SvelteKit app to Vercel 72 | 73 | You have the freedom to deploy your SvelteKit app to your hosting provider of choice. With Vercel and GitHub being a popular choice, we'll cover the basics of that approach. 74 | 75 | 1. Create a GitHub repository from this project. [Learn more](https://docs.github.com/en/migrations/importing-source-code/using-the-command-line-to-import-source-code/adding-locally-hosted-code-to-github). 76 | 2. Create a new Vercel project and connect it to your Github repository. 77 | 3. Set the `Root Directory` to your SvelteKit app. 78 | 4. Configure your Environment Variables. 79 | 80 | #### 3. Invite a collaborator 81 | 82 | Now that you’ve deployed your SvelteKit application and Sanity Studio, you can optionally invite a collaborator to your Studio. Open up [Manage](https://www.sanity.io/manage), select your project and click "Invite project members" 83 | 84 | They will be able to access the deployed Studio, where you can collaborate together on creating content. 85 | 86 | ## Resources 87 | 88 | - [Sanity documentation](https://www.sanity.io/docs/) 89 | - [SvelteKit documentation](https://svelte.dev/docs/kit/introduction/) 90 | - [Join the Sanity Community](https://slack.sanity.io) 91 | - [Learn Sanity](https://www.sanity.io/learn) 92 | --------------------------------------------------------------------------------