├── .env.example
├── .eslintrc.cjs
├── .gitignore
├── .npmrc
├── .vscode
├── extensions.json
└── launch.json
├── README.md
├── astro.config.mjs
├── env.d.ts
├── package.json
├── pnpm-lock.yaml
├── prettier.config.cjs
├── public
└── favicon
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── mstile-150x150.png
│ ├── safari-pinned-tab.svg
│ └── site.webmanifest
├── src
├── components
│ ├── Author.astro
│ ├── Card.astro
│ ├── Footer.astro
│ ├── Logo.astro
│ ├── Metadata.astro
│ ├── Navigation.astro
│ └── PostBody.tsx
├── env.d.ts
├── images
│ └── cosmic.svg
├── layouts
│ └── Layout.astro
├── lib
│ └── cosmic.js
└── pages
│ ├── blog
│ ├── [slug].astro
│ └── index.astro
│ └── index.astro
├── tailwind.config.cjs
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | PUBLIC_COSMIC_BUCKET_SLUG=
2 | PUBLIC_COSMIC_READ_KEY=
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import("eslint").Linter.Config} */
2 | module.exports = {
3 | extends: ['plugin:astro/recommended'],
4 | parser: '@typescript-eslint/parser',
5 | parserOptions: {
6 | tsconfigRootDir: __dirname,
7 | sourceType: 'module',
8 | ecmaVersion: 'latest'
9 | },
10 | overrides: [
11 | {
12 | files: ['*.astro'],
13 | parser: 'astro-eslint-parser',
14 | parserOptions: {
15 | parser: '@typescript-eslint/parser',
16 | extraFileExtensions: ['.astro', '.tsx', '.ts', '.jsx', '.js']
17 | },
18 | rules: {
19 | // override/add rules settings here, such as:
20 | // "astro/no-set-html-directive": "error"
21 | }
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 | .output/
4 |
5 | # dependencies
6 | node_modules/
7 |
8 | # logs
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | pnpm-debug.log*
13 |
14 |
15 | # environment variables
16 | .env
17 | .env.production
18 |
19 | # macOS-specific files
20 | .DS_Store
21 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # Expose Astro dependencies for `pnpm` users
2 | shamefully-hoist=true
3 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["astro-build.astro-vscode"],
3 | "unwantedRecommendations": []
4 | }
5 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple Astro Blog built with Astro and Cosmic
2 |
3 | To build this app, we’re going to use the following technologies:
4 |
5 | - [Astro](https://astro.build/) - A React framework for production that makes it easy to spin up a full-stack application.
6 | - [Cosmic](https://www.cosmicjs.com/) - A Headless CMS enables the independence of the data (content) layer and gives us the ability to quickly manage template content.
7 | - [Tailwind CSS](https://tailwindcss.com/) - A performant utility-first CSS framework that can be composed directly in your markup.
8 |
9 | ### Links
10 |
11 | - [Install the template](https://www.cosmicjs.com/marketplace/templates/simple-astro-blog)
12 | - [View the live demo](https://simple-astro-blog.vercel.app/)
13 |
14 | ## Screenshots
15 |
16 | 
17 |
18 | 
19 |
20 | ## Getting started
21 |
22 | ### Environment Variables
23 |
24 | You'll need to create an .env file in the root of the project. Log in to Cosmic and from Bucket Settings > API Access take the following values:
25 |
26 | ```
27 | //.env
28 | PUBLIC_COSMIC_BUCKET_SLUG=your_cosmic_slug
29 | PUBLIC_COSMIC_READ_KEY=your_cosmic_read_key
30 | ```
31 |
32 | Install the dependencies with
33 |
34 | ```
35 | pnpm install
36 | # or
37 | yarn install
38 | # or
39 | npm install
40 | ```
41 |
42 | Then run the development server:
43 |
44 | ```
45 | pnpm run dev
46 | # or
47 | yarn dev
48 | # or
49 | npm run dev
50 | ```
51 |
52 | Open [http://localhost:3000](http://localhost:3000/) with your browser to see the result.
53 |
54 | You can start editing the page by modifying `src/pages/index.astro`. The page auto-updates as you edit the file.
55 |
56 | ## Deploy on Vercel
57 |
58 |
Use the following button to deploy to Vercel . You will need to add API accesss keys as environment variables. Find these in Bucket Settings > API Access .
59 |
60 |
61 |
62 |
63 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
64 |
65 | Your feedback and contributions are welcome!
66 |
--------------------------------------------------------------------------------
/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'astro/config'
2 | import react from '@astrojs/react'
3 | import tailwind from '@astrojs/tailwind'
4 | import robotsTxt from 'astro-robots-txt'
5 |
6 | // https://astro.build/config
7 | export default defineConfig({
8 | integrations: [tailwind(), react(), robotsTxt()],
9 | site: 'https://simple-astro-blog.vercel.app'
10 | })
11 |
--------------------------------------------------------------------------------
/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/basics",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "astro dev",
7 | "start": "astro dev",
8 | "build": "astro build",
9 | "preview": "astro preview",
10 | "astro": "astro",
11 | "lint": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\" && eslint --fix \"src/**/*.{js,ts,jsx,tsx,astro}\""
12 | },
13 | "dependencies": {
14 | "@astrojs/react": "^3.0.0",
15 | "@astrojs/tailwind": "^5.0.0",
16 | "@astrojs/ts-plugin": "^1.0.4",
17 | "@cosmicjs/sdk": "^1.0.5",
18 | "astro": "^3.0.8",
19 | "date-fns": "^2.29.2",
20 | "react-markdown": "^8.0.3",
21 | "sharp": "^0.32.1",
22 | "tailwindcss": "^3.3.2"
23 | },
24 | "devDependencies": {
25 | "@types/node": "^18.7.15",
26 | "@typescript-eslint/parser": "^5.59.2",
27 | "astro-robots-txt": "^0.3.8",
28 | "eslint": "^8.39.0",
29 | "eslint-plugin-astro": "^0.26.1",
30 | "eslint-plugin-jsx-a11y": "^6.7.1",
31 | "prettier": "^2.8.8",
32 | "prettier-config-standard": "^5.0.0",
33 | "prettier-plugin-astro": "^0.8.0",
34 | "prettier-plugin-tailwindcss": "^0.2.8",
35 | "react": "^18.2.0",
36 | "react-dom": "^18.2.0",
37 | "typescript": "^5.2.2"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/prettier.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import("prettier").Config} */
2 | module.exports = {
3 | ...require('prettier-config-standard'),
4 | pluginSearchDirs: [__dirname],
5 | plugins: [
6 | require.resolve('prettier-plugin-astro'),
7 | require('prettier-plugin-tailwindcss')
8 | ],
9 | overrides: [
10 | {
11 | files: '*.astro',
12 | options: {
13 | parser: 'astro'
14 | }
15 | }
16 | ],
17 | tailwindConfig: './tailwind.config.cjs'
18 | }
19 |
--------------------------------------------------------------------------------
/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/simple-astro-blog/75a2c891b7d6200580095a08245c62b9d2ab7863/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/simple-astro-blog/75a2c891b7d6200580095a08245c62b9d2ab7863/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/simple-astro-blog/75a2c891b7d6200580095a08245c62b9d2ab7863/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/simple-astro-blog/75a2c891b7d6200580095a08245c62b9d2ab7863/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/simple-astro-blog/75a2c891b7d6200580095a08245c62b9d2ab7863/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/simple-astro-blog/75a2c891b7d6200580095a08245c62b9d2ab7863/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/public/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/simple-astro-blog/75a2c891b7d6200580095a08245c62b9d2ab7863/public/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/public/favicon/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.14, written by Peter Selinger 2001-2017
9 |
10 |
12 |
41 |
72 |
82 |
93 |
124 |
137 |
165 |
175 |
176 |
177 |
--------------------------------------------------------------------------------
/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Author.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { Image } from 'astro:assets'
3 | const { name, imageUrl, about, linkedIn, twitter, gitHub } = Astro.props
4 | ---
5 |
6 |
70 |
--------------------------------------------------------------------------------
/src/components/Card.astro:
--------------------------------------------------------------------------------
1 | ---
2 | export interface Props {
3 | title: string
4 | body: string
5 | href: string
6 | tags: {
7 | title: string
8 | metadata: {
9 | color?: string
10 | }
11 | }[]
12 | }
13 |
14 | const { href, title, body, tags } = Astro.props
15 | ---
16 |
17 |
18 |
19 |
20 |
21 | {title}
22 |
23 |
24 | {body}
25 |
26 | {
27 | tags && (
28 |
29 | {tags?.map(
30 | (tag: { title: string; metadata: { color?: string } }) => (
31 |
34 | {tag.title}
35 |
36 | )
37 | )}
38 |
39 | )
40 | }
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/Footer.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Logo from './Logo.astro'
3 | ---
4 |
5 |
6 |
7 |
8 |
9 | Home
10 |
11 |
12 | Blog
13 |
14 |
15 |
16 |
17 | Powered by
18 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/Logo.astro:
--------------------------------------------------------------------------------
1 | ---
2 | const iconColor = 'fill-[#28ABE2]'
3 |
4 | const { width, height } = Astro.props
5 | ---
6 |
7 |
15 |
18 |
21 |
24 |
27 |
30 |
33 |
36 |
39 |
42 |
45 |
48 |
51 |
54 |
57 |
60 |
63 |
66 |
69 |
70 |
--------------------------------------------------------------------------------
/src/components/Metadata.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { getConfig } from '../lib/cosmic'
3 | const config = await getConfig()
4 | const defaultTitle = config.metadata.website_title
5 | const defaultDescription = config.metadata.website_description
6 | const defaultImage = config.metadata.og_image.imgix_url
7 | const defaultUrl = config.metadata.og_url
8 | const { title, description, imageUrl } = Astro.props
9 | ---
10 |
11 |
12 | {title || defaultTitle}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
41 |
47 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/components/Navigation.astro:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 |
6 |
10 |
27 |
43 | Home
45 |
46 |
71 |
72 | Blog
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/components/PostBody.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactMarkdown from 'react-markdown'
3 |
4 | const components: {} = {
5 | p: (p: { children: string }) => {
6 | return {p.children}
7 | },
8 | a: (a: { children: string; href: string }) => {
9 | return (
10 |
14 | {a.children}
15 |
16 | )
17 | },
18 | h2: (h2: { children: string }) => {
19 | return {h2.children}
20 | },
21 | h3: (h3: { children: string }) => {
22 | return {h3.children}
23 | },
24 | img: (img: { src: string; alt: string }) => {
25 | return (
26 |
33 | )
34 | }
35 | }
36 |
37 | const PostBody: React.FC<{ content: string }> = ({ content }) => {
38 | return (
39 |
42 | {content}
43 |
44 | )
45 | }
46 | export default PostBody
47 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/images/cosmic.svg:
--------------------------------------------------------------------------------
1 |
7 |
14 |
15 |
16 |
20 |
28 |
32 |
36 |
40 |
44 |
48 |
52 |
60 |
64 |
68 |
76 |
77 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/src/layouts/Layout.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Footer from '../components/Footer.astro'
3 | import Metadata from '../components/Metadata.astro'
4 | import Navigation from '../components/Navigation.astro'
5 | const { title, description, imageUrl } = Astro.props
6 | import { ViewTransitions } from 'astro:transitions'
7 | ---
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | The source code for this blog is{' '}
18 |
22 | available on GitHub .
24 |
25 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/lib/cosmic.js:
--------------------------------------------------------------------------------
1 | import { createBucketClient } from '@cosmicjs/sdk'
2 |
3 | const BUCKET_SLUG = import.meta.env.PUBLIC_COSMIC_BUCKET_SLUG
4 | const READ_KEY = import.meta.env.PUBLIC_COSMIC_READ_KEY
5 |
6 | const cosmic = createBucketClient({
7 | bucketSlug: BUCKET_SLUG,
8 | readKey: READ_KEY
9 | })
10 |
11 | export async function getAllPosts() {
12 | const data = await cosmic.objects
13 | .find({
14 | type: 'posts'
15 | })
16 | .props('title,slug,metadata,created_at')
17 | .sort('-created_at')
18 | .depth(2)
19 | return data.objects
20 | }
21 |
22 | export async function getFeaturedPost() {
23 | const data = await cosmic.objects
24 | .findOne({
25 | type: 'featured-post',
26 | slug: 'set-featured-post'
27 | })
28 | .props('metadata')
29 | .depth(2)
30 | return data.object.metadata.post
31 | }
32 |
33 | export async function getConfig() {
34 | const data = await cosmic.objects
35 | .findOne({ type: 'config', slug: 'config' })
36 | .props('metadata')
37 | .depth(1)
38 | return data.object
39 | }
40 |
--------------------------------------------------------------------------------
/src/pages/blog/[slug].astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { getAllPosts, getConfig } from '../../lib/cosmic'
3 | import Layout from '../../layouts/Layout.astro'
4 | import PostBody from '../../components/PostBody'
5 | import { Image } from 'astro:assets'
6 | import { parseISO, format } from 'date-fns'
7 | import Author from '../../components/Author.astro'
8 | const config = await getConfig()
9 | const defaultMetaImage = config.metadata.og_image.imgix_url
10 | import { getFeaturedPost } from '../../lib/cosmic'
11 |
12 | interface Props {
13 | slug: string
14 | post: {
15 | slug: string
16 | title: string
17 | created_at: string
18 | metadata: {
19 | excerpt: string
20 | content: string
21 | cover_image?: {
22 | url: string
23 | imgix_url: string
24 | }
25 | author?: {
26 | title: string
27 | slug: string
28 | metadata: {
29 | avatar?: {
30 | url: string
31 | imgix_url: string
32 | }
33 | about?: string
34 | linkedin_link?: string
35 | twitter_link?: string
36 | github_link?: string
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
43 | export async function getStaticPaths() {
44 | const data = (await getAllPosts()) || []
45 |
46 | return data.map((post: Props) => {
47 | return {
48 | params: { slug: post.slug },
49 | props: { post }
50 | }
51 | })
52 | }
53 |
54 | const { post } = Astro.props
55 | const featuredPost = await getFeaturedPost()
56 | ---
57 |
58 |
63 |
64 |
65 |
66 | {post.title}
67 |
68 |
69 |
70 | {
71 | post.metadata.author?.metadata.avatar && (
72 |
81 | )
82 | }
83 |
84 | {
85 | post.metadata.author?.title ? (
86 | {post.metadata.author?.title}
87 | ) : (
88 | ''
89 | )
90 | }
91 | {format(parseISO(post.created_at), 'MM/dd/yyyy')}
92 |
93 |
94 |
95 | {
96 | post.metadata.cover_image && (
97 |
110 | )
111 | }
112 |
113 | {
114 | post.metadata?.author && (
115 |
131 | )
132 | }
133 |
134 |
135 |
--------------------------------------------------------------------------------
/src/pages/blog/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from '../../layouts/Layout.astro'
3 | import Card from '../../components/Card.astro'
4 | import { getAllPosts } from '../../lib/cosmic'
5 | import { Image } from 'astro:assets'
6 | import { getFeaturedPost } from '../../lib/cosmic'
7 |
8 | interface Props {
9 | title: string
10 | slug: string
11 | metadata: {
12 | excerpt: string
13 | tags: {
14 | title: string
15 | metadata: {
16 | color?: string
17 | }
18 | }[]
19 | cover_image?: {
20 | url: string
21 | imgix_url: string
22 | }
23 | }
24 | }
25 |
26 | const data = await getAllPosts()
27 | const featuredPost = await getFeaturedPost()
28 | const tags = featuredPost.metadata.tags
29 | ---
30 |
31 |
32 | Blog Posts
33 |
34 |
74 |
75 |
76 | {
77 | data
78 | .filter((post: Props) => post.title !== featuredPost.title)
79 | .map((post: Props) => (
80 | tag)}
85 | />
86 | ))
87 | }
88 |
89 |
90 |
--------------------------------------------------------------------------------
/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from '../layouts/Layout.astro'
3 | import Card from '../components/Card.astro'
4 | import { getAllPosts } from '../lib/cosmic'
5 |
6 | const data = await getAllPosts()
7 |
8 | export interface Post {
9 | title: string
10 | slug: string
11 | metadata: {
12 | excerpt: string
13 | tags: {
14 | title: string
15 | metadata: {
16 | color?: string
17 | }
18 | }[]
19 | }
20 | }
21 | ---
22 |
23 |
24 |
25 | Simple Astro Blog
29 |
30 |
31 | This Blog is built with:
32 |
56 |
57 | Blog Posts
58 |
59 |
60 | {
61 | data.map((post: Post) => (
62 | tag)}
67 | />
68 | ))
69 | }
70 |
71 |
72 |
73 | Blog page
78 |
79 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/strict",
3 | "compilerOptions": {
4 | "jsx": "react",
5 | "strictNullChecks": true,
6 | "baseUrl": ".",
7 | "paths": {
8 | "@components/*": ["src/components/*"],
9 | "@assets/*": ["src/assets/*"],
10 | "@layouts/*": ["src/layouts/*"],
11 | "@pages/*": ["src/pages/*"]
12 | },
13 | "plugins": [
14 | {
15 | "name": "@astrojs/ts-plugin"
16 | }
17 | ],
18 | "allowJs": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------