├── 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 | [](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
8 | [](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 |
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 |
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 |
18 | {posts.map((post) => (
19 | -
20 | {post.title}
21 |
22 | ))}
23 |
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 |
--------------------------------------------------------------------------------