├── README.md ├── frontend ├── .gitignore ├── .vscode │ ├── extensions.json │ └── launch.json ├── README.md ├── astro.config.mjs ├── package-lock.json ├── package.json ├── public │ └── favicon.svg ├── src │ ├── components │ │ ├── PortableText.astro │ │ └── PortableTextImage.astro │ ├── env.d.ts │ ├── pages │ │ ├── index.astro │ │ └── post │ │ │ └── [slug].astro │ └── sanity │ │ └── urlForImage.js └── tsconfig.json └── studio ├── .eslintrc ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── sanity.cli.ts ├── sanity.config.ts ├── schemas ├── author.ts ├── blockContent.ts ├── category.ts ├── index.ts └── post.ts ├── static └── .gitkeep └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | # Example files to build your blog with Astro and Sanity 2 | 3 | These files are used in a guide called [Build your blog with Astro and Sanity](https://www.sanity.io/guides/sanity-astro-blog). 4 | 5 | You'll find two applications: 6 | 7 | - `studio` is [Sanity Studio](https://www.sanity.io/studio), a Single Page Application in which you manage your content, it connects to the hosted API that has all your blog's content 8 | - `frontend` is an [Astro](https://astro.build/)-based frontend which will generate static pages from each of the posts you've created in your studio 9 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /frontend/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /frontend/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Astro Starter Kit: Minimal 2 | 3 | ``` 4 | npm create astro@latest -- --template minimal 5 | ``` 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal) 8 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/s/github/withastro/astro/tree/latest/examples/minimal) 9 | 10 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 11 | 12 | ## 🚀 Project Structure 13 | 14 | Inside of your Astro project, you'll see the following folders and files: 15 | 16 | ``` 17 | / 18 | ├── public/ 19 | ├── src/ 20 | │ └── pages/ 21 | │ └── index.astro 22 | └── package.json 23 | ``` 24 | 25 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 26 | 27 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 28 | 29 | Any static assets, like images, can be placed in the `public/` directory. 30 | 31 | ## 🧞 Commands 32 | 33 | All commands are run from the root of the project, from a terminal: 34 | 35 | | Command | Action | 36 | | :--------------------- | :----------------------------------------------- | 37 | | `npm install` | Installs dependencies | 38 | | `npm run dev` | Starts local dev server at `localhost:3000` | 39 | | `npm run build` | Build your production site to `./dist/` | 40 | | `npm run preview` | Preview your build locally, before deploying | 41 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 42 | | `npm run astro --help` | Get help using the Astro CLI | 43 | 44 | ## 👀 Want to learn more? 45 | 46 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 47 | -------------------------------------------------------------------------------- /frontend/astro.config.mjs: -------------------------------------------------------------------------------- 1 | // my-blog/frontend/astro.config.mjs 2 | 3 | import { defineConfig } from "astro/config"; 4 | import sanity from "@sanity/astro"; 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | integrations: [ 9 | sanity({ 10 | projectId: "v1kdinau", 11 | dataset: "production", 12 | apiVersion: "2023-02-08", 13 | useCdn: false, 14 | }), 15 | ], 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/minimal", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "astro dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@sanity/astro": "^1.0.0", 15 | "@sanity/image-url": "^1.0.2", 16 | "astro": "^2.0.6", 17 | "astro-portabletext": "^0.9.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/components/PortableText.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { PortableText as PortableTextInternal } from 'astro-portabletext' 3 | import PortableTextImage from "./PortableTextImage.astro"; 4 | 5 | const { portableText } = Astro.props; 6 | 7 | const components = { 8 | type: { 9 | image: PortableTextImage, 10 | } 11 | }; 12 | --- 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/components/PortableTextImage.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { urlForImage } from '../sanity/urlForImage'; 3 | 4 | const {asset, alt} = Astro.props.node 5 | 6 | const url = urlForImage(asset).url() 7 | const webpUrl = urlForImage(asset).format('webp').url() 8 | --- 9 | 10 | 11 | 15 | {alt} 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { useSanityClient } from "@sanity/astro"; 3 | 4 | const posts = await useSanityClient().fetch(`*[_type == "post"]`); 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | My blog 12 | 13 | 14 |

My blog

15 | { 16 | posts && ( 17 | 24 | ) 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /frontend/src/pages/post/[slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { useSanityClient } from '@sanity/astro'; 3 | import PortableText from '../../components/PortableText.astro'; 4 | 5 | export async function getStaticPaths() { 6 | const posts = await useSanityClient().fetch(`*[_type == "post"]`); 7 | 8 | return posts.map((post) => { 9 | return { 10 | params: { 11 | slug: post.slug?.current || '', 12 | }, 13 | props: { ...post }, 14 | }; 15 | }); 16 | } 17 | 18 | const { title, body } = Astro.props; 19 | 20 | --- 21 | 22 |

{title}

23 | -------------------------------------------------------------------------------- /frontend/src/sanity/urlForImage.js: -------------------------------------------------------------------------------- 1 | import { useSanityClient } from '@sanity/astro'; 2 | import imageUrlBuilder from "@sanity/image-url"; 3 | 4 | export const imageBuilder = imageUrlBuilder(useSanityClient()); 5 | 6 | export function urlForImage(source) { 7 | return imageBuilder.image(source); 8 | } -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base" 3 | } -------------------------------------------------------------------------------- /studio/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/eslint-config-studio" 3 | } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | - Check out the example frontend: [React/Next.js](https://github.com/sanity-io/tutorial-sanity-blog-react-next) 9 | - [Read the blog post about this template](https://www.sanity.io/blog/build-your-own-blog-with-sanity-and-next-js?utm_source=readme) 10 | - [Join the community Slack](https://slack.sanity.io/?utm_source=readme) 11 | - [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme) 12 | -------------------------------------------------------------------------------- /studio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remove-me-astro-test", 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.0.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-is": "^18.2.0", 22 | "sanity": "^3.0.0", 23 | "styled-components": "^5.2.0" 24 | }, 25 | "devDependencies": { 26 | "@sanity/eslint-config-studio": "^2.0.1", 27 | "eslint": "^8.6.0", 28 | "prettier": "^2.8.3", 29 | "typescript": "^4.0.0" 30 | }, 31 | "prettier": { 32 | "semi": false, 33 | "printWidth": 100, 34 | "bracketSpacing": false, 35 | "singleQuote": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /studio/sanity.cli.ts: -------------------------------------------------------------------------------- 1 | import {defineCliConfig} from 'sanity/cli' 2 | 3 | export default defineCliConfig({ 4 | api: { 5 | projectId: 'v1kdinau', 6 | dataset: 'production' 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /studio/sanity.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'sanity' 2 | import {deskTool} from 'sanity/desk' 3 | import {visionTool} from '@sanity/vision' 4 | import {schemaTypes} from './schemas' 5 | 6 | export default defineConfig({ 7 | name: 'default', 8 | title: 'remove-me-astro-test', 9 | 10 | projectId: 'v1kdinau', 11 | dataset: 'production', 12 | 13 | plugins: [deskTool(), visionTool()], 14 | 15 | schema: { 16 | types: schemaTypes, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /studio/schemas/author.ts: -------------------------------------------------------------------------------- 1 | import {defineField, defineType} from 'sanity' 2 | 3 | export default defineType({ 4 | name: 'author', 5 | title: 'Author', 6 | type: 'document', 7 | fields: [ 8 | defineField({ 9 | name: 'name', 10 | title: 'Name', 11 | type: 'string', 12 | }), 13 | defineField({ 14 | name: 'slug', 15 | title: 'Slug', 16 | type: 'slug', 17 | options: { 18 | source: 'name', 19 | maxLength: 96, 20 | }, 21 | }), 22 | defineField({ 23 | name: 'image', 24 | title: 'Image', 25 | type: 'image', 26 | options: { 27 | hotspot: true, 28 | }, 29 | }), 30 | defineField({ 31 | name: 'bio', 32 | title: 'Bio', 33 | type: 'array', 34 | of: [ 35 | { 36 | title: 'Block', 37 | type: 'block', 38 | styles: [{title: 'Normal', value: 'normal'}], 39 | lists: [], 40 | }, 41 | ], 42 | }), 43 | ], 44 | preview: { 45 | select: { 46 | title: 'name', 47 | media: 'image', 48 | }, 49 | }, 50 | }) 51 | -------------------------------------------------------------------------------- /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 | // You can add additional types here. Note that you can't use 60 | // primitive types such as 'string' and 'number' in the same array 61 | // as a block type. 62 | defineArrayMember({ 63 | type: 'image', 64 | options: {hotspot: true}, 65 | }), 66 | ], 67 | }) 68 | -------------------------------------------------------------------------------- /studio/schemas/category.ts: -------------------------------------------------------------------------------- 1 | import {defineField, defineType} from 'sanity' 2 | 3 | export default defineType({ 4 | name: 'category', 5 | title: 'Category', 6 | type: 'document', 7 | fields: [ 8 | defineField({ 9 | name: 'title', 10 | title: 'Title', 11 | type: 'string', 12 | }), 13 | defineField({ 14 | name: 'description', 15 | title: 'Description', 16 | type: 'text', 17 | }), 18 | ], 19 | }) 20 | -------------------------------------------------------------------------------- /studio/schemas/index.ts: -------------------------------------------------------------------------------- 1 | import blockContent from './blockContent' 2 | import category from './category' 3 | import post from './post' 4 | import author from './author' 5 | 6 | export const schemaTypes = [post, author, category, blockContent] 7 | -------------------------------------------------------------------------------- /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 | options: { 18 | source: 'title', 19 | maxLength: 96, 20 | }, 21 | }), 22 | defineField({ 23 | name: 'author', 24 | title: 'Author', 25 | type: 'reference', 26 | to: {type: 'author'}, 27 | }), 28 | defineField({ 29 | name: 'mainImage', 30 | title: 'Main image', 31 | type: 'image', 32 | options: { 33 | hotspot: true, 34 | }, 35 | }), 36 | defineField({ 37 | name: 'categories', 38 | title: 'Categories', 39 | type: 'array', 40 | of: [{type: 'reference', to: {type: 'category'}}], 41 | }), 42 | defineField({ 43 | name: 'publishedAt', 44 | title: 'Published at', 45 | type: 'datetime', 46 | }), 47 | defineField({ 48 | name: 'body', 49 | title: 'Body', 50 | type: 'blockContent', 51 | }), 52 | ], 53 | 54 | preview: { 55 | select: { 56 | title: 'title', 57 | author: 'author.name', 58 | media: 'mainImage', 59 | }, 60 | prepare(selection) { 61 | const {author} = selection 62 | return {...selection, subtitle: author && `by ${author}`} 63 | }, 64 | }, 65 | }) 66 | -------------------------------------------------------------------------------- /studio/static/.gitkeep: -------------------------------------------------------------------------------- 1 | Files placed here will be served by the Sanity server under the `/static`-prefix 2 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------