├── .gitignore ├── backend ├── modules │ ├── default-page │ │ ├── views │ │ │ └── page.html │ │ └── index.js │ └── @apostrophecms │ │ ├── home-page │ │ ├── views │ │ │ └── page.html │ │ └── index.js │ │ ├── page │ │ ├── views │ │ │ └── notFound.html │ │ └── index.js │ │ ├── express │ │ └── index.js │ │ ├── asset │ │ └── index.js │ │ ├── settings │ │ └── index.js │ │ ├── blog │ │ └── index.js │ │ └── layout-column-widget │ │ └── index.js ├── public │ └── images │ │ └── logo.png ├── lib │ └── area.js ├── eslint.config.js ├── deployment │ ├── README │ ├── settings.staging │ ├── migrate │ ├── rsync_exclude.txt │ ├── stop │ ├── settings │ ├── dependencies │ └── start ├── views │ └── layout.html ├── .gitignore ├── LICENSE ├── app.js ├── README.md ├── scripts │ ├── sync-down │ └── sync-up └── package.json ├── frontend ├── tsconfig.json ├── src │ ├── env.d.ts │ ├── templates │ │ ├── NotFoundPage.astro │ │ ├── DefaultPage.astro │ │ ├── BlogShowPage.astro │ │ ├── index.js │ │ ├── BlogIndexPage.astro │ │ └── HomePage.astro │ ├── widgets │ │ ├── RichTextWidget.astro │ │ ├── FileWidget.astro │ │ ├── index.js │ │ ├── ImageWidget.astro │ │ └── VideoWidget.astro │ ├── styles │ │ └── app.css │ ├── components │ │ ├── Figure.astro │ │ └── ImageLink.astro │ └── pages │ │ └── [...slug].astro ├── .vscode │ ├── extensions.json │ └── launch.json ├── public │ ├── images │ │ └── image-widget-placeholder.jpg │ └── favicon.svg ├── .gitignore ├── postcss.config.js ├── package.json ├── README.md └── astro.config.mjs ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | /node_modules 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /backend/modules/default-page/views/page.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base" 3 | } 4 | -------------------------------------------------------------------------------- /backend/modules/@apostrophecms/home-page/views/page.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | -------------------------------------------------------------------------------- /backend/modules/@apostrophecms/page/views/notFound.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | -------------------------------------------------------------------------------- /frontend/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /backend/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/starter-kit-astro-essentials/main/backend/public/images/logo.png -------------------------------------------------------------------------------- /frontend/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /backend/lib/area.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '@apostrophecms/image': {}, 3 | '@apostrophecms/video': {}, 4 | '@apostrophecms/rich-text': {} 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/templates/NotFoundPage.astro: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 |
4 |

Not Found

5 |

Sorry, that page was not found.

6 |
7 | -------------------------------------------------------------------------------- /frontend/public/images/image-widget-placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/starter-kit-astro-essentials/main/frontend/public/images/image-widget-placeholder.jpg -------------------------------------------------------------------------------- /backend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import apostrophe from 'eslint-config-apostrophe'; 2 | import { defineConfig } from 'eslint/config'; 3 | 4 | export default defineConfig([ 5 | apostrophe 6 | ]); 7 | -------------------------------------------------------------------------------- /backend/modules/@apostrophecms/express/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | session: { 4 | // If this still says `undefined`, set a real secret! 5 | secret: undefined 6 | } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/widgets/RichTextWidget.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { widget } = Astro.props; 3 | const { content } = widget; 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/widgets/FileWidget.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { widget } = Astro.props; 3 | const { _file } = widget; 4 | const file = _file[0]; 5 | --- 6 |
7 | { (file && {file.title}) || 'No file selected' } 8 |
9 | -------------------------------------------------------------------------------- /backend/deployment/README: -------------------------------------------------------------------------------- 1 | This is a deployment folder for use with Stagecoach. 2 | 3 | You don't have to use Stagecoach. 4 | 5 | It's just a neat solution for deploying node apps. 6 | 7 | See: 8 | 9 | http://github.com/apostrophecms/stagecoach 10 | -------------------------------------------------------------------------------- /backend/modules/@apostrophecms/asset/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // When not in production, refresh the page on restart 3 | options: { 4 | refreshOnRestart: true, 5 | // Not for use with Astro, which has its own 6 | hmr: false 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /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/src/styles/app.css: -------------------------------------------------------------------------------- 1 | /* Image Widget */ 2 | .img-widget { 3 | margin: 0; 4 | width: 100%; 5 | } 6 | .img-widget img { 7 | width: 100%; 8 | height: auto; 9 | display: block; 10 | object-fit: cover; 11 | aspect-ratio: var(--aspect-ratio, auto); 12 | } 13 | -------------------------------------------------------------------------------- /backend/deployment/settings.staging: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Settings specific to the 'master' deployment target. 4 | # USER is the ssh user, SERVER is the ssh host. USER should 5 | # match the USER setting in /opt/stagecoach/settings on 6 | # the server 7 | 8 | USER=nodeapps 9 | SERVER=staging.apos.dev 10 | -------------------------------------------------------------------------------- /frontend/src/templates/DefaultPage.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AposArea from '@apostrophecms/apostrophe-astro/components/AposArea.astro'; 3 | const { page, user, query } = Astro.props.aposData; 4 | const { main } = page; 5 | --- 6 | 7 |
8 |

{ page.title }

9 | 10 |
11 | -------------------------------------------------------------------------------- /backend/deployment/migrate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export NODE_ENV=production 4 | 5 | # Run any necessary database migration tasks that should happen while the 6 | # site is paused here. 7 | # 8 | # We don't have any, 3.x policy is safe migrations only. -Tom 9 | 10 | # node app @apostrophecms/migration:migrate 11 | # 12 | #echo "Site migrated" 13 | -------------------------------------------------------------------------------- /backend/views/layout.html: -------------------------------------------------------------------------------- 1 | {# Automatically extends the right outer layout and also handles AJAX siutations #} 2 | {% extends data.outerLayout %} 3 | 4 | {% block main %} 5 |

6 | This project is a backend for astro. Leave it running separately and access 7 | the astro project's URL for all edits. 8 |

9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | package-lock.json 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | 17 | # environment variables 18 | .env 19 | .env.production 20 | 21 | # macOS-specific files 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | import postcssViewportToContainerToggle from 'postcss-viewport-to-container-toggle'; 2 | 3 | export default { 4 | // Is the site still editable in production like a normal Apos Site, 5 | // if yes we need the plugin for all builds 6 | plugins: [ 7 | postcssViewportToContainerToggle({ 8 | modifierAttr: 'data-breakpoint-preview-mode', 9 | debug: true, 10 | transform: null 11 | }) 12 | ] 13 | } -------------------------------------------------------------------------------- /backend/modules/@apostrophecms/settings/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | subforms: { 4 | title: { 5 | fields: [ 'title' ], 6 | protection: true, 7 | reload: true 8 | }, 9 | changePassword: { 10 | fields: [ 'password' ] 11 | } 12 | }, 13 | 14 | groups: { 15 | account: { 16 | label: 'Account', 17 | subforms: [ 'title', 'changePassword' ] 18 | } 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /frontend/src/templates/BlogShowPage.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AposArea from '@apostrophecms/apostrophe-astro/components/AposArea.astro'; 3 | import dayjs from 'dayjs'; 4 | 5 | const { page, piece, user, query } = Astro.props.aposData; 6 | const { main } = piece; 7 | --- 8 | 9 |
10 |

{ piece.title }

11 |

12 | Released On { dayjs(piece.publishedAt).format('MMMM D, YYYY') } 13 |

14 | 15 |
16 | -------------------------------------------------------------------------------- /backend/modules/@apostrophecms/page/index.js: -------------------------------------------------------------------------------- 1 | // This configures the @apostrophecms/page module to add a "home" page type to the 2 | // pages menu 3 | 4 | export default { 5 | options: { 6 | types: [ 7 | { 8 | name: 'default-page', 9 | label: 'Default' 10 | }, 11 | { 12 | name: '@apostrophecms/blog-page', 13 | label: 'Blog Page' 14 | }, 15 | { 16 | name: '@apostrophecms/home-page', 17 | label: 'Home' 18 | } 19 | ] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | /locales 3 | npm-debug.log 4 | /data 5 | /public/uploads 6 | /public/apos-minified 7 | /data/temp/uploadfs 8 | node_modules 9 | # This folder is created on the fly and contains symlinks updated at startup (we'll come up with a Windows solution that actually copies things) 10 | /public/modules 11 | # Don't commit build files 12 | /apos-build 13 | /public/apos-frontend 14 | # Don't commit masters generated on the fly at startup, these import all the rest 15 | /public/css/master-*.less 16 | .jshintrc 17 | -------------------------------------------------------------------------------- /backend/modules/@apostrophecms/blog/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fields: { 3 | add: { 4 | main: { 5 | type: 'area', 6 | options: { 7 | widgets: { 8 | '@apostrophecms/rich-text': {}, 9 | '@apostrophecms/image': {}, 10 | '@apostrophecms/video': {}, 11 | 'two-column': {} 12 | } 13 | } 14 | } 15 | }, 16 | group: { 17 | basics: { 18 | fields: [ 'title', 'publishedAt', 'main' ] 19 | } 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /backend/modules/@apostrophecms/layout-column-widget/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fields(self, options) { 3 | return { 4 | add: { 5 | content: { 6 | type: 'area', 7 | label: 'Main Content', 8 | options: { 9 | widgets: { 10 | '@apostrophecms/rich-text': {}, 11 | '@apostrophecms/image': {}, 12 | '@apostrophecms/video': {}, 13 | '@apostrophecms/file': {}, 14 | } 15 | } 16 | } 17 | } 18 | }; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/src/templates/index.js: -------------------------------------------------------------------------------- 1 | import HomePage from './HomePage.astro'; 2 | import DefaultPage from './DefaultPage.astro'; 3 | import BlogIndexPage from './BlogIndexPage.astro'; 4 | import BlogShowPage from './BlogShowPage.astro'; 5 | import NotFoundPage from './NotFoundPage.astro'; 6 | 7 | const templateComponents = { 8 | '@apostrophecms/home-page': HomePage, 9 | 'default-page': DefaultPage, 10 | '@apostrophecms/blog-page:index': BlogIndexPage, 11 | '@apostrophecms/blog-page:show': BlogShowPage, 12 | '@apostrophecms/page:notFound': NotFoundPage 13 | }; 14 | 15 | export default templateComponents; 16 | -------------------------------------------------------------------------------- /backend/deployment/rsync_exclude.txt: -------------------------------------------------------------------------------- 1 | # List files and folders that shouldn't be deployed (such as data folders and runtime status files) here. 2 | # In our projects .git and .gitignore are good candidates, also 'data' which contains persistent files 3 | # that are *not* part of deployment. A good place for things like data/port, data/pid, and any 4 | # sqlite databases or static web content you may need 5 | data 6 | temp 7 | public/uploads 8 | public/apos-frontend 9 | .git 10 | .gitignore 11 | # We don't deploy these anymore, instead we always 'npm install' to ensure 12 | # that any compiled C++ modules are built for the right architecture 13 | node_modules 14 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-frontend", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "dev": "cross-env APOS_EXTERNAL_FRONT_KEY=dev astro dev", 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "serve": "HOST=0.0.0.0 PORT=4321 node ./dist/server/entry.mjs", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@apostrophecms/apostrophe-astro": "^1.4.0", 15 | "@astrojs/node": "^9.4.3", 16 | "astro": "^5.13.7", 17 | "cross-env": "^10.1.0", 18 | "dayjs": "^1.11.10", 19 | "postcss-viewport-to-container-toggle": "^1.1.0", 20 | "vite": "^7.1.5" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/modules/default-page/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extend: '@apostrophecms/page-type', 3 | options: { 4 | label: 'Default Page' 5 | }, 6 | fields: { 7 | add: { 8 | main: { 9 | type: 'area', 10 | options: { 11 | widgets: { 12 | '@apostrophecms/layout': {}, 13 | '@apostrophecms/rich-text': {}, 14 | '@apostrophecms/image': {}, 15 | '@apostrophecms/video': {} 16 | } 17 | } 18 | } 19 | }, 20 | group: { 21 | basics: { 22 | label: 'Basics', 23 | fields: [ 24 | 'title', 25 | 'main' 26 | ] 27 | } 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/src/widgets/index.js: -------------------------------------------------------------------------------- 1 | import RichTextWidget from './RichTextWidget.astro'; 2 | import ImageWidget from './ImageWidget.astro'; 3 | import VideoWidget from './VideoWidget.astro'; 4 | import LayoutWidget from '@apostrophecms/apostrophe-astro/widgets/LayoutWidget.astro'; 5 | import LayoutColumnWidget from '@apostrophecms/apostrophe-astro/widgets/LayoutColumnWidget.astro'; 6 | import FileWidget from './FileWidget.astro'; 7 | 8 | const widgetComponents = { 9 | '@apostrophecms/rich-text': RichTextWidget, 10 | '@apostrophecms/image': ImageWidget, 11 | '@apostrophecms/video': VideoWidget, 12 | '@apostrophecms/layout': LayoutWidget, 13 | '@apostrophecms/layout-column': LayoutColumnWidget, 14 | '@apostrophecms/file': FileWidget, 15 | }; 16 | 17 | export default widgetComponents; 18 | -------------------------------------------------------------------------------- /frontend/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/src/components/Figure.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import ImageLink from "./ImageLink.astro"; 3 | 4 | export interface Props { 5 | image: { 6 | src: string; 7 | alt?: string | null; 8 | style?: string | null; 9 | srcset?: string; 10 | objectPosition: string; 11 | width?: number | string; 12 | height?: number | string; 13 | aspectRatio?: string; 14 | }; 15 | caption?: string; 16 | link?: { 17 | url: string; 18 | title?: string | null; 19 | target?: string | null; 20 | rel?: string | null; 21 | }; 22 | style?: string | null; 23 | } 24 | 25 | const { image, caption, link, style } = Astro.props; 26 | --- 27 | 28 |
29 | 30 | {caption &&
{caption}
} 31 |
32 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # apostrophecms/astro-frontend 2 | 3 | This is an Astro-based website, with editable content powered by ApostropheCMS. 4 | 5 | To complete your project, you will need to fork *two* repositories: 6 | this one (the starting point for your Astro project), and the 7 | [`apostrophecms/starter-kit-astro`](https://github.com/apostrophecms/starter-kit-astro) 8 | project to power content editing, store your content, handle media uploads and 9 | provide a complete and user-friendly on-page editing experience. This kind of 10 | side-by-side development separating the "front end" or "front for back" (Astro) 11 | from the content management system (ApostropheCMS) is common when working with 12 | Astro. 13 | 14 | Please see the [`@apostrophecms/apostrophe-astro`](https://github.com/apostrophecms/apostrophe-astro) documentation 15 | for complete information about how to get started with this project. 16 | -------------------------------------------------------------------------------- /backend/deployment/stop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export NODE_ENV=production 4 | 5 | # Shut the site down, for instance by tweaking a .htaccess file to display 6 | # a 'please wait' notice, or stopping a node server 7 | 8 | if [ ! -f "app.js" ]; then 9 | echo "I don't see app.js in the current directory." 10 | exit 1 11 | fi 12 | 13 | # Stop the node app via 'forever'. You'll get a harmless warning if the app 14 | # was not already running. Use `pwd` to make sure we have a full path, 15 | # forever is otherwise easily confused and will stop every server with 16 | # the same filename 17 | forever stop `pwd`/app.js && echo "Site stopped" 18 | 19 | # Stop the app without 'forever'. We recommend using 'forever' for node apps, 20 | # but this may be your best bet for non-node apps 21 | # 22 | # if [ -f "data/pid" ]; then 23 | # kill `cat data/pid` 24 | # rm data/pid 25 | # echo "Site stopped" 26 | # else 27 | # echo "Site was not running" 28 | # fi 29 | 30 | -------------------------------------------------------------------------------- /frontend/src/pages/[...slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import aposPageFetch from "@apostrophecms/apostrophe-astro/lib/aposPageFetch.js"; 3 | import AposLayout from "@apostrophecms/apostrophe-astro/components/layouts/AposLayout.astro"; 4 | import AposTemplate from "@apostrophecms/apostrophe-astro/components/AposTemplate.astro"; 5 | import "../styles/app.css"; 6 | 7 | const aposData = await aposPageFetch(Astro.request); 8 | const bodyClass = `myclass`; 9 | 10 | if (aposData.redirect) { 11 | return Astro.redirect(aposData.url, aposData.status); 12 | } 13 | if (aposData.notFound) { 14 | Astro.response.status = 404; 15 | } 16 | --- 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /backend/deployment/settings: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Settings shared by all targets (staging, production, etc). Usually the 4 | # shortname of the project (which is also the hostname for the frontend 5 | # proxy server used for staging sites) and the directory name. For our 6 | # web apps that use sc-proxy we make sure each is a subdirectory 7 | # of /opt/stagecoach/apps 8 | 9 | # Should match the repo name = short name = everything name! 10 | PROJECT=a3-boilerplate 11 | 12 | DIR=/opt/stagecoach/apps/$PROJECT 13 | 14 | # Adjust the PATH environment variable on the remote host. Here's an example 15 | # for deploying to MacPorts 16 | #ADJUST_PATH='export PATH=/opt/local/bin:$PATH' 17 | 18 | # ... But you probably won't need to on real servers. I just find it handy for 19 | # testing parts of stagecoach locally on a Mac. : is an acceptable "no-op" (do-nothing) statement 20 | ADJUST_PATH=':' 21 | 22 | # ssh port. Sensible people leave this set to 22 but it's common to do the 23 | # "security by obscurity" thing alas 24 | SSH_PORT=22 25 | -------------------------------------------------------------------------------- /frontend/src/templates/BlogIndexPage.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import dayjs from 'dayjs'; 3 | import setParameter from '@apostrophecms/apostrophe-astro/lib/aposSetQueryParameter.js'; 4 | 5 | const { 6 | page, 7 | user, 8 | query, 9 | piecesFilters, 10 | pieces, 11 | currentPage, 12 | totalPages 13 | } = Astro.props.aposData; 14 | 15 | const pages = []; 16 | for (let i = 1; (i <= totalPages); i++) { 17 | pages.push({ 18 | number: i, 19 | current: page === currentPage, 20 | url: setParameter(Astro.url, 'page', i) 21 | }); 22 | } 23 | --- 24 | 25 |
26 |

{ page.title }

27 | 28 |

Blog Posts

29 | 30 | {pieces.map(piece => ( 31 |

32 | Released On { dayjs(piece.publishedAt).format('MMMM D, YYYY') } 33 |

34 |

35 | { piece.title } 36 |

37 | ))} 38 | 39 | {pages.map(page => ( 40 | {page.number} 43 | 44 | ))} 45 |
-------------------------------------------------------------------------------- /backend/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Apostrophe Technologies 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /backend/modules/@apostrophecms/home-page/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | label: 'Home Page' 4 | }, 5 | fields: { 6 | add: { 7 | main: { 8 | type: 'area', 9 | options: { 10 | widgets: { 11 | '@apostrophecms/layout': {}, 12 | '@apostrophecms/rich-text': {}, 13 | '@apostrophecms/image': {}, 14 | '@apostrophecms/video': {}, 15 | '@apostrophecms/file': {} 16 | } 17 | } 18 | }, 19 | objectField: { 20 | type: 'object', 21 | fields: { 22 | add: { 23 | content: { 24 | type: 'area', 25 | options: { 26 | widgets: { 27 | '@apostrophecms/image': {} 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | }, 35 | group: { 36 | basics: { 37 | label: 'Basics', 38 | fields: [ 39 | 'title', 40 | 'main' 41 | ] 42 | } 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /backend/app.js: -------------------------------------------------------------------------------- 1 | import apostrophe from 'apostrophe'; 2 | 3 | apostrophe({ 4 | root: import.meta, 5 | shortName: 'starter-kit-astro', 6 | bundles: [ '@apostrophecms/blog' ], 7 | modules: { 8 | // Apostrophe module configuration 9 | // ******************************* 10 | // 11 | // NOTE: most configuration occurs in the respective modules' directories. 12 | // See modules/@apostrophecms/page/index.js for an example. 13 | // 14 | // Any modules that are not present by default in Apostrophe must at least 15 | // have a minimal configuration here to turn them on: `moduleName: {}` 16 | // *********************************************************************** 17 | '@apostrophecms/vite': {}, 18 | // `className` options set custom CSS classes for Apostrophe core widgets. 19 | '@apostrophecms/rich-text-widget': { 20 | }, 21 | '@apostrophecms/image-widget': { 22 | }, 23 | '@apostrophecms/video-widget': { 24 | }, 25 | // The project's first custom page type. 26 | 'default-page': {}, 27 | 'two-column-widget': {}, 28 | '@apostrophecms/blog': {}, 29 | '@apostrophecms/blog-page': {} 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Apostrophe starter kit for Astro integration 2 | 3 | This is intended as a starting point for an Apostrophe project with a 4 | separate front end powered by [Astro](https://astro.build/). 5 | 6 | To complete your project, you will need to fork **both** this project 7 | and our [astro-frontend](https://github.com/apostrophecms/astro-frontend) project. 8 | 9 | This kind of side-by-side development separating the "front end" or "front for back" 10 | (Astro) from the content management system (ApostropheCMS) is common when working 11 | with Astro. 12 | 13 | Also see the [Apostrophe documentation](https://v3.docs.apostrophecms.org/). Note 14 | that documentation regarding Nunjucks templates, frontend assets, etc. does not 15 | apply when using an Astro front end, because rendering pages becomes the responsibility of 16 | Astro. On the other hand, documentation about schema fields, page types, piece 17 | types, widget types, event handlers and many other topics remains very 18 | relevant when working with Astro. 19 | 20 | Please see the [@apostrophecms/apostrophe-astro](https://github.com/apostrophecms/apostrophe-astro) documentation 21 | for complete information about how to get started with this project. 22 | -------------------------------------------------------------------------------- /frontend/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import node from '@astrojs/node'; 3 | import apostrophe from '@apostrophecms/apostrophe-astro'; 4 | 5 | export default defineConfig({ 6 | output: 'server', 7 | server: { 8 | port: process.env.PORT ? parseInt(process.env.PORT) : 4321, 9 | // Required for some hosting, like Heroku 10 | // host: true 11 | }, 12 | adapter: node({ 13 | mode: 'standalone' 14 | }), 15 | integrations: [ 16 | apostrophe({ 17 | aposHost: 'http://localhost:3000', 18 | widgetsMapping: './src/widgets', 19 | templatesMapping: './src/templates', 20 | includeResponseHeaders: [ 21 | 'content-security-policy', 22 | 'strict-transport-security', 23 | 'x-frame-options', 24 | 'referrer-policy', 25 | 'cache-control' 26 | ], 27 | excludeRequestHeaders: [ 28 | // Must exclude this for separate apostrophe and astro hosting to work 29 | // 'host' 30 | ] 31 | }) 32 | ], 33 | vite: { 34 | ssr: { 35 | // Do not externalize the @apostrophecms/apostrophe-astro plugin, we need 36 | // to be able to use virtual: URLs there 37 | noExternal: [ '@apostrophecms/apostrophe-astro' ], 38 | } 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /backend/scripts/sync-down: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TARGET="$1" 4 | if [ -z "$TARGET" ]; then 5 | echo "Usage: ./scripts/sync-down production" 6 | echo "(or as appropriate)" 7 | exit 1 8 | fi 9 | 10 | source deployment/settings || exit 1 11 | source "deployment/settings.$TARGET" || exit 1 12 | 13 | #Enter the Mongo DB name (should be same locally and remotely). 14 | dbName=$PROJECT 15 | 16 | #Enter the Project name (should be what you called it for stagecoach). 17 | projectName=$PROJECT 18 | 19 | #Enter the SSH username/url for the remote server. 20 | remoteSSH="-p $SSH_PORT $USER@$SERVER" 21 | rsyncTransport="ssh -p $SSH_PORT" 22 | rsyncDestination="$USER@$SERVER" 23 | 24 | echo "Syncing MongoDB" 25 | ssh $remoteSSH mongodump -d $dbName -o /tmp/mongodump.$dbName && 26 | rsync -av -e "$rsyncTransport" $rsyncDestination:/tmp/mongodump.$dbName/ /tmp/mongodump.$dbName && 27 | ssh $remoteSSH rm -rf /tmp/mongodump.$dbName && 28 | # noIndexRestore increases compatibility between 3.x and 2.x, 29 | # and Apostrophe will recreate the indexes correctly at startup 30 | mongorestore --noIndexRestore --drop -d $dbName /tmp/mongodump.$dbName/$dbName && 31 | echo "Syncing Files" && 32 | rsync -av --delete -e "$rsyncTransport" $rsyncDestination:/opt/stagecoach/apps/$projectName/uploads/ ./public/uploads && 33 | echo "Synced down from $TARGET" 34 | echo "YOU MUST RESTART THE SITE LOCALLY TO REBUILD THE MONGODB INDEXES." 35 | -------------------------------------------------------------------------------- /frontend/src/components/ImageLink.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { image, link } = Astro.props; 3 | 4 | export interface Props { 5 | image: { 6 | src: string; 7 | alt?: string | null; 8 | style?: string | null; 9 | srcset?: string; 10 | objectPosition: string; 11 | width?: number | string; 12 | height?: number | string; 13 | aspectRatio?: string; 14 | }; 15 | link?: { 16 | url: string; 17 | title?: string | null; 18 | target?: string | null; 19 | rel?: string | null; 20 | }; 21 | } 22 | const style = 23 | `object-position: ${image.objectPosition};${image.aspectRatio ? `--aspect-ratio: ${image.aspectRatio};` : ""} 24 | `.trim(); 25 | --- 26 | 27 | 28 | { 29 | !link && ( 30 | {image.alt} 41 | ) 42 | } 43 | { 44 | link && ( 45 | 46 | {image.alt} 57 | 58 | ) 59 | } 60 | 61 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starter-kit-astro", 3 | "version": "1.0.0", 4 | "description": "Apostrophe starter kit project for Astro integration", 5 | "main": "app.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node app", 9 | "dev": "cross-env APOS_EXTERNAL_FRONT_KEY=dev nodemon", 10 | "build": "cross-env NODE_ENV=production node app @apostrophecms/asset:build", 11 | "serve": "cross-env NODE_ENV=production node app", 12 | "release": "npm install && npm run build && npm run migrate", 13 | "migrate": "cross-env NODE_ENV=production node app @apostrophecms/migration:migrate" 14 | }, 15 | "nodemonConfig": { 16 | "delay": 1000, 17 | "verbose": true, 18 | "watch": [ 19 | "./app.js", 20 | "./modules/**/*", 21 | "./lib/**/*.js", 22 | "./views/**/*.html" 23 | ], 24 | "ignoreRoot": [ 25 | ".git" 26 | ], 27 | "ignore": [ 28 | "**/ui/apos/", 29 | "**/ui/src/", 30 | "**ui/public/", 31 | "locales/*.json", 32 | "public/uploads/", 33 | "public/apos-frontend/*.js", 34 | "data/" 35 | ], 36 | "ext": "json, js, html, scss, vue" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/apostrophecms/combined-astro-starter-kit" 41 | }, 42 | "author": "Apostrophe Technologies, Inc.", 43 | "license": "MIT", 44 | "dependencies": { 45 | "@apostrophecms/blog": "^1.0.4", 46 | "@apostrophecms/vite": "^1.1.0", 47 | "apostrophe": "^4.23.0", 48 | "cross-env": "^10.1.0" 49 | }, 50 | "devDependencies": { 51 | "eslint-config-apostrophe": "^6.0.1", 52 | "nodemon": "^3.0.1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "combined-astro-starter-kit", 3 | "version": "1.0.0", 4 | "description": "Combined ApostropheCMS plus Astro starter kit", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "postinstall": "cd frontend && echo '\\n💡 running npm install in frontend/' && npm install && cd ../backend && echo '\\n💡 running npm install in backend/' && npm install && echo '\\n💡 combined npm install complete'", 9 | "update": "cd frontend && echo '\\n💡 running npm update in frontend/' && npm update && cd ../backend && echo '\\n💡 running npm update in backend/' && npm update && echo '\\n💡 combined npm update complete'", 10 | "build": "npm run build-frontend && npm run build-backend && echo '\\n💡 combined build complete'", 11 | "build-frontend": "echo '\\n💡 running npm run build in frontend/' && cd frontend && npm run build", 12 | "build-backend": "echo '\\n💡 running npm run build in backend/' && cd backend && npm run build", 13 | "migrate": "cd backend && npm run migrate", 14 | "serve-frontend": "cd frontend && npm run serve", 15 | "serve-backend": "cd backend && npm run serve" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/apostrophecms/combined-astro-starter-kit.git" 20 | }, 21 | "keywords": [ 22 | "astro", 23 | "apostrophecms", 24 | "apostrophe" 25 | ], 26 | "author": "Apostrophe Technologies Inc.", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/apostrophecms/combined-astro-starter-kit/issues" 30 | }, 31 | "homepage": "https://github.com/apostrophecms/combined-astro-starter-kit#readme" 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/templates/HomePage.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AposArea from '@apostrophecms/apostrophe-astro/components/AposArea.astro'; 3 | const { page, user, query } = Astro.props.aposData; 4 | const { main } = page; 5 | --- 6 | 7 |
8 |

9 | Welcome to Apostrophe 3 10 |

11 | { user ? '' : 12 |
13 |

First time spinning up the Apostrophe Astro combined project?

14 |

15 | Use the credentials created during setup with the Apostrophe CLI or create a new user with this command 16 | in your apostrophe project folder (not Astro): 17 |

18 |
19 |         Command Line
20 |         
21 |           node app @apostrophecms/user:add myUsername admin
22 |         
23 |       
24 |

25 | Then log in here. 26 |

27 |
28 | 29 | } 30 |

31 | For a guide on how to configure and customize this project, please check out the apostrophe-astro documentation. 32 |

33 |
34 | { (user && !query?.['apos-edit']) ? 35 |

36 | Enter Edit mode from the admin bar 👆 to begin. 37 |

38 | : 39 |

40 | Add and edit content below in the content area. 👇 41 |

42 | } 43 | 44 |
45 |
46 | -------------------------------------------------------------------------------- /backend/scripts/sync-up: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TARGET="$1" 4 | if [ -z "$TARGET" ]; then 5 | echo "Usage: ./scripts/sync-up production" 6 | echo "(or as appropriate)" 7 | echo 8 | echo "THIS WILL CLOBBER EVERYTHING ON THE" 9 | echo "TARGET SITE. MAKE SURE THAT IS WHAT" 10 | echo "YOU WANT!" 11 | exit 1 12 | fi 13 | 14 | read -p "THIS WILL CRUSH THE SITE'S CONTENT ON $TARGET. Are you sure? " -n 1 -r 15 | echo 16 | if [[ ! $REPLY =~ ^[Yy]$ ]] 17 | then 18 | exit 1 19 | fi 20 | 21 | source deployment/settings || exit 1 22 | source "deployment/settings.$TARGET" || exit 1 23 | 24 | #Enter the Mongo DB name (should be same locally and remotely). 25 | dbName=$PROJECT 26 | 27 | #Enter the Project name (should be what you called it for stagecoach). 28 | projectName=$PROJECT 29 | 30 | #Enter the SSH username/url for the remote server. 31 | remoteSSH="-p $SSH_PORT $USER@$SERVER" 32 | rsyncTransport="ssh -p $SSH_PORT" 33 | rsyncDestination="$USER@$SERVER" 34 | 35 | echo "Syncing MongoDB" 36 | mongodump -d $dbName -o /tmp/mongodump.$dbName && 37 | echo rsync -av -e "$rsyncTransport" /tmp/mongodump.$dbName/ $rsyncDestination:/tmp/mongodump.$dbName && 38 | rsync -av -e "$rsyncTransport" /tmp/mongodump.$dbName/ $rsyncDestination:/tmp/mongodump.$dbName && 39 | rm -rf /tmp/mongodump.$dbName && 40 | # noIndexRestore increases compatibility between 3.x and 2.x, 41 | # and Apostrophe will recreate the indexes correctly at startup 42 | ssh $remoteSSH mongorestore --noIndexRestore --drop -d $dbName /tmp/mongodump.$dbName/$dbName && 43 | echo "Syncing Files" && 44 | rsync -av --delete -e "$rsyncTransport" ./public/uploads/ $rsyncDestination:/opt/stagecoach/apps/$projectName/uploads && 45 | echo "Synced up to $TARGET" 46 | echo "YOU MUST RESTART THE SITE ON $TARGET TO REBUILD THE MONGODB INDEXES." 47 | -------------------------------------------------------------------------------- /backend/deployment/dependencies: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export NODE_ENV=production 4 | 5 | # Also a good place to ensure any data folders 6 | # that are *not* supposed to be replaced on every deployment exist 7 | # and create a symlink to them from the latest deployment directory. 8 | 9 | # The real 'data' folder is shared. It lives two levels up and one over 10 | # (we're in a deployment dir, which is a subdir of 'deployments', which 11 | # is a subdir of the project's main dir) 12 | 13 | HERE=`pwd` 14 | mkdir -p ../../data 15 | ln -s ../../data $HERE/data 16 | 17 | # We also have a shared uploads folder which is convenient to keep 18 | # in a separate place so we don't have to have two express.static calls 19 | 20 | mkdir -p ../../uploads 21 | ln -s ../../../uploads $HERE/public/uploads 22 | 23 | # Install any dependencies that can't just be rsynced over with 24 | # the deployment. Example: node apps have npm modules in a 25 | # node_modules folder. These may contain compiled C++ code that 26 | # won't work portably from one server to another. 27 | 28 | # This script runs after the rsync, but before the 'stop' script, 29 | # so your app is not down during the npm installation. 30 | 31 | # Make sure node_modules exists so npm doesn't go searching 32 | # up the filesystem tree 33 | mkdir -p node_modules 34 | 35 | # If there is no package.json file then we don't need npm install 36 | if [ -f './package.json' ]; then 37 | # Install npm modules 38 | # Use a suitable version of Python 39 | # export PYTHON=/usr/bin/python26 40 | npm install 41 | if [ $? -ne 0 ]; then 42 | echo "Error during npm install!" 43 | exit 1 44 | fi 45 | fi 46 | 47 | node app @apostrophecms/migration:migrate 48 | # Generate new static asset files for this 49 | # deployment of the app without shutting down 50 | # (TODO: for 3.0 this is actually disruptive because 51 | # we don't have a generation identifier yet) 52 | npm run build 53 | -------------------------------------------------------------------------------- /frontend/src/widgets/ImageWidget.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { 3 | getAttachmentUrl, 4 | getAttachmentSrcset, 5 | getFocalPoint, 6 | getWidth, 7 | getHeight, 8 | } from "@apostrophecms/apostrophe-astro/lib/attachment.js"; 9 | import { slugify } from "@apostrophecms/apostrophe-astro/lib/util.js"; 10 | import placeholderImg from "../../public/images/image-widget-placeholder.jpg"; 11 | import Figure from "../components/Figure.astro"; 12 | 13 | const { widget = {} } = Astro.props; 14 | const placeholder = widget?.aposPlaceholder; 15 | const imageObj = widget?._image?.[0]; 16 | 17 | const src = placeholder ? placeholderImg.src : getAttachmentUrl(imageObj); 18 | 19 | const srcset = placeholder ? "" : getAttachmentSrcset(imageObj); 20 | const objectPosition = placeholder ? "center center" : getFocalPoint(imageObj); 21 | // Only add width/height if they exist to prevent layout shift 22 | const width = imageObj ? getWidth(imageObj) : undefined; 23 | const height = imageObj ? getHeight(imageObj) : undefined; 24 | const aspectRatio = width && height ? `${width}/${height}` : undefined; 25 | 26 | let link = { 27 | url: widget.linkHref, 28 | title: widget.linkHrefTitle || widget.caption, 29 | target: widget.linkTarget, 30 | rel: null, 31 | }; 32 | const image = { 33 | src, 34 | alt: imageObj?.alt || imageObj?.attachment?._alt || "", 35 | style: "img-widget__image", 36 | srcset, 37 | objectPosition, 38 | width, 39 | height, 40 | aspectRatio, 41 | }; 42 | let style = "img-widget"; 43 | 44 | switch (widget.linkTo) { 45 | case "none": { 46 | link = null; 47 | break; 48 | } 49 | case "_url": { 50 | link.rel = widget.target === "_blank" ? "noopener noreferrer" : null; 51 | break; 52 | } 53 | default: { 54 | // slugify the linkTo value to reference the widget field. 55 | const name = "_" + slugify(widget.linkTo); 56 | const item = widget[name]?.[0]; 57 | link.url = item?._url; 58 | link.title = widget.linkTitle || item?.title; 59 | break; 60 | } 61 | } 62 | --- 63 | 64 |
65 | -------------------------------------------------------------------------------- /backend/deployment/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make the site live again, for instance by tweaking a .htaccess file 4 | # or starting a node server. In this example we also set up a 5 | # data/port file so that sc-proxy.js can figure out what port 6 | # to forward traffic to for this site. The idea is that every 7 | # folder in /var/webapps represents a separate project with a separate 8 | # node process, each listening on a specific port, and they all 9 | # need traffic forwarded from a reverse proxy server on port 80 10 | 11 | # Useful for debugging 12 | #set -x verbose 13 | 14 | # Express should not reveal information on errors, 15 | # also optimizes Express performance 16 | export NODE_ENV=production 17 | 18 | if [ ! -f "app.js" ]; then 19 | echo "I don't see app.js in the current directory." 20 | exit 1 21 | fi 22 | 23 | # Assign a port number if we don't yet have one 24 | 25 | if [ -f "data/port" ]; then 26 | PORT=`cat data/port` 27 | else 28 | # No port set yet for this site. Scan and sort the existing port numbers if any, 29 | # grab the highest existing one 30 | PORT=`cat ../../../*/data/port 2>/dev/null | sort -n | tail -1` 31 | if [ "$PORT" == "" ]; then 32 | echo "First app ever, assigning port 3000" 33 | PORT=3000 34 | else 35 | # Bash is much nicer than sh! We can do math without tears! 36 | let PORT+=1 37 | fi 38 | echo $PORT > data/port 39 | echo "First startup, chose port $PORT for this site" 40 | fi 41 | 42 | # Run the app via 'forever' so that it restarts automatically if it fails 43 | # Use `pwd` to make sure we have a full path, forever is otherwise easily confused 44 | # and will stop every server with the same filename 45 | 46 | # Use a "for" loop. A classic single-port file will do the 47 | # right thing, but so will a file with multiple port numbers 48 | # for load balancing across multiple cores 49 | for port in $PORT 50 | do 51 | export PORT=$port 52 | forever --minUptime=1000 --spinSleepTime=10000 -o data/console.log -e data/error.log start `pwd`/app.js && echo "Site started" 53 | done 54 | 55 | # Run the app without 'forever'. Record the process id so 'stop' can kill it later. 56 | # We recommend installing 'forever' instead for node apps. For non-node apps this code 57 | # may be helpful 58 | # 59 | # node app.js >> data/console.log 2>&1 & 60 | # PID=$! 61 | # echo $PID > data/pid 62 | # 63 | #echo "Site started" 64 | -------------------------------------------------------------------------------- /frontend/src/widgets/VideoWidget.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { widget } = Astro.props; 3 | const placeholder = widget?.aposPlaceholder ? 'true' : ''; 4 | const url = widget?.video?.url; 5 | --- 6 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ApostropheCMS + Astro Essentials Starter Kit: 2 | 3 | **Build lightning-fast websites with the editing experience your content team actually wants to use.** 4 | 5 | This powerful combination gives you Astro's incredible performance and developer experience, plus ApostropheCMS's intuitive in-context editing and content management capabilities. No more choosing between speed and usability—get both. 6 | 7 | ## ApostropheCMS + Astro Starter Kits 8 | 9 | **Choose the right foundation for your project:** 10 | 11 | ## 🎯 Astro Essentials Starter Kit (This Repository) 12 | **Perfect if you want:** A clean, minimal foundation to build your own design system 13 | 14 | - **Minimal & Non-opinionated**: Essential building blocks without imposed design decisions 15 | - **Core Components**: Basic page types, essential widgets, and clean architecture 16 | - **Maximum Flexibility**: Build your own styling approach and component library 17 | - **Learning Focus**: Understand the ApostropheCMS + Astro integration from the ground up 18 | - **Best for**: Developers who want full creative control and custom design systems 19 | 20 | ## 🌟 [Apollo Starter Kit](https://github.com/apostrophecms/apollo) 21 | **Perfect if you want:** A production-ready foundation with beautiful design included 22 | 23 | - **Production-Ready Design**: Complete Bulma-based design system with modern styling 24 | - **Rich Feature Set**: Advanced widgets, layouts, and pre-styled components 25 | - **Faster Time-to-Market**: Launch professional sites with minimal additional styling 26 | - **Content-Rich Sites**: Built-in blog, author relationships, and content management features 27 | - **Best for**: Teams who want to focus on content and functionality over design from scratch 28 | 29 | --- 30 | 31 | **Still deciding?** Both use the same powerful ApostropheCMS + Astro architecture. You can always start with the Essentials Starter Kit and add features, or begin with [Apollo](https://github.com/apostrophecms/apollo) and customize the design to match your brand. 32 | 33 | **Ready for enterprise features?** [Contact us about Apollo Pro](https://apostrophecms.com/contact-us) for advanced permissions, automated translations, SEO optimization, and more professional capabilities. 34 | 35 | - [ApostropheCMS + Astro Essentials Starter Kit:](#apostrophecms--astro-essentials-starter-kit) 36 | - [ApostropheCMS + Astro Starter Kits](#apostrophecms--astro-starter-kits) 37 | - [🎯 Astro Essentials Starter Kit (This Repository)](#-astro-essentials-starter-kit-this-repository) 38 | - [🌟 Apollo Starter Kit](#-apollo-starter-kit) 39 | - [🎯 What This Starter Provides](#-what-this-starter-provides) 40 | - [✨ Why Use This Combination](#-why-use-this-combination) 41 | - [🎯 What Makes This Special](#-what-makes-this-special) 42 | - [🚀 Quick Start](#-quick-start) 43 | - [Prerequisites](#prerequisites) 44 | - [Get Running in Minutes](#get-running-in-minutes) 45 | - [🏗️ Architecture Overview](#️-architecture-overview) 46 | - [How It Works](#how-it-works) 47 | - [Project Structure](#project-structure) 48 | - [For ApostropheCMS Developers](#for-apostrophecms-developers) 49 | - [For Astro Developers](#for-astro-developers) 50 | - [🖼️ Image Helper Functions](#️-image-helper-functions) 51 | - [Overview](#overview) 52 | - [Working with Image Relationships](#working-with-image-relationships) 53 | - [Working with Direct Attachments](#working-with-direct-attachments) 54 | - [Image Cropping and Sizes](#image-cropping-and-sizes) 55 | - [Working with Focal Points](#working-with-focal-points) 56 | - [Core Functions Reference](#core-functions-reference) 57 | - [🚀 Deployment Options](#-deployment-options) 58 | - [**ApostropheCMS Hosting** (Recommended)](#apostrophecms-hosting-recommended) 59 | - [**DIY Deployment**](#diy-deployment) 60 | - [Backend (ApostropheCMS) Deployment](#backend-apostrophecms-deployment) 61 | - [Frontend (Astro) Deployment](#frontend-astro-deployment) 62 | - [Netlify Deployment Example](#netlify-deployment-example) 63 | - [🚑 Need Help?](#-need-help) 64 | - [📚 Learn More](#-learn-more) 65 | - [🎯 Ready to Build Something Amazing?](#-ready-to-build-something-amazing) 66 | 67 | 68 | ## 🎯 What This Starter Provides 69 | 70 | This is a **minimal, non-opinionated foundation** that demonstrates the ApostropheCMS + Astro integration without imposing design decisions on your project. You get the essential building blocks: 71 | 72 | - **Basic Page Types**: Home page, default content page, and simple blog structure 73 | - **Core Widgets**: Rich text, images, video, and a layout widget 74 | - **Clean Architecture**: Well-organized but unopinionated code structure 75 | - **Integration Examples**: Working demonstrations of content fetching and rendering 76 | 77 | **Perfect for:** Developers who want to understand the integration and build their own design system on top, rather than those looking for a ready-to-launch theme. 78 | 79 | ## ✨ Why Use This Combination 80 | 81 | **For Developers:** 82 | - 🏎️ **Blazing Fast**: Astro's server-side rendering + smart hydration = incredible Core Web Vitals scores 83 | - 🛠️ **Modern DX**: Write components in React, Vue, Svelte, or vanilla JS—your choice 84 | - 🔧 **Zero API Boilerplate**: The `apostrophe-astro` package handles all the backend communication automatically 85 | - 🚀 **Seamless Hosting**: Deploy your ApostropheCMS + Astro Essentials project with [ApostropheCMS hosting](https://apostrophecms.com/hosting) for zero-config deployment, or choose from flexible alternatives like Netlify, Vercel, and Cloudflare 86 | 87 | **For Content Teams:** 88 | - ✏️ **True WYSIWYG**: Edit content directly on the live site with ApostropheCMS's in-context editing 89 | - 🎯 **No Learning Curve**: Familiar, intuitive admin interface that non-technical users love 90 | - 🔄 **Instant Previews**: See changes immediately without switching between admin panels and preview modes 91 | - 👥 **Powerful Workflows**: Built-in user roles, content approval, and publishing controls 92 | 93 | **For Everyone:** 94 | - 🏗️ **Solid Foundation**: Essential building blocks with core widgets and basic page types 95 | - 📚 **Non-Opinionated**: Clean starter that doesn't impose design decisions on your project 96 | - 📈 **Room to Grow**: Architecture that scales from simple sites to complex applications 97 | - 💰 **Open Source**: Free to use with optional Pro features available 98 | 99 | --- 100 | 101 | ## 🎯 What Makes This Special 102 | 103 | Unlike typical headless setups where content editors work in separate admin panels, this combination delivers **in-context editing**. Content teams can click directly on the live site to edit—while you keep the modern development experience of Astro. 104 | 105 | **The key:** The [`apostrophe-astro` package](https://github.com/apostrophecms/apostrophe-astro) creates a seamless bridge between your Astro frontend and ApostropheCMS backend, handling authentication, content fetching, and real-time updates automatically. 106 | 107 | ## 🚀 Quick Start 108 | 109 | ### Prerequisites 110 | - Node.js v22 or later 111 | - MongoDB v6.0 or later ([setup guide](https://docs.apostrophecms.org/guide/development-setup.html)) 112 | - Windows users: [WSL2 required](https://docs.apostrophecms.org/cookbook/windows-development.html) 113 | 114 | ### Get Running in Minutes 115 | 116 | The codebases located in the `backend` and `frontend` folders should be treated as interlinked but separate projects. 117 | 118 | To simplify dependency management, this repository includes several root-level scripts for convenience. The `postinstall` script automatically installs dependencies for both the `frontend` and `backend` folders when you run `npm install` at the root. 119 | 120 | 1. **Clone the repo and install dependencies** 121 | ```bash 122 | git clone https://github.com/apostrophecms/starter-kit-astro-essentials.git 123 | cd starter-kit-astro-essentials 124 | npm install 125 | ``` 126 | 2. **Set up environment variables** 127 | Both projects need an `APOS_EXTERNAL_FRONT_KEY` environment variable set to the same value for authentication. Open two terminals: 128 | - **Mac/Linux users**: One terminal in `frontend` folder, one in `backend` folder 129 | - **Windows users**: WSL terminal for `backend` folder, WSL or Windows terminal for `frontend` folder 130 | 131 | ```bash 132 | # In both terminal windows 133 | export APOS_EXTERNAL_FRONT_KEY=my-secret-key 134 | ``` 135 | 136 | The `astro.config.mjs` file uses default values, but if running the backend on a different port and/or a different server, also set: 137 | ```bash 138 | export APOS_HOST=your-backend-url 139 | ``` 140 | 141 | 3. **Start development servers** 142 | 143 | ```bash 144 | # Terminal 1 - Backend (use WSL on Windows) 145 | cd backend && npm run dev 146 | 147 | # Terminal 2 - Frontend 148 | cd frontend && npm run dev 149 | ``` 150 | 151 | > **Note:** Astro is less stringent about project setup in development mode. Before deployment, run `npm run build` followed by `npm run preview` in the `frontend` folder to test production behavior. We don't recommend using the root `npm run serve-frontend` script during development - it's used for Apostrophe hosting. 152 | 153 | Visit `http://localhost:4321` and start building! 🎉 154 | 155 | --- 156 | 157 | ## 🏗️ Architecture Overview 158 | 159 | 160 | ### How It Works 161 | This project utilizes ApostropheCMS as a headless backend with Astro as a frontend. What sets this apart from typical headless setups is the [apostrophe-astro](https://github.com/apostrophecms/apostrophe-astro) package in the Astro frontend project. This enables full use of the ApostropheCMS Admin UI, including in-context editing, while largely automating content fetching from the backend without writing REST API calls. 162 | 163 | ### Project Structure 164 | ``` 165 | ├── backend/ # ApostropheCMS application 166 | │ ├── modules/ # Custom modules (pages, pieces, widgets) 167 | │ ├── app.js # Main configuration 168 | │ └── ... 169 | ├── frontend/ # Astro application 170 | │ ├── src/ 171 | │ │ ├── pages/ # Single [...slug].astro route 172 | │ │ ├── templates/ # Page templates 173 | │ │ ├── widgets/ # Widget templates 174 | │ │ └── components/ # Astro components 175 | │ ├── astro.config.mjs # Astro configuration 176 | │ └── ... 177 | └── README.md # This file 178 | └── package.json. # Whole project scripts 179 | ``` 180 | 181 | ### For ApostropheCMS Developers 182 | 183 | If you've worked with ApostropheCMS previously, the backend should look familiar. Custom modules for pages, pieces, and widgets are in the `modules` folder, with core module configuration in `modules/@apostrophecms`. 184 | 185 | **What stays the same:** 186 | - Module registration in `app.js` 187 | - Page types added to `modules/@apostrophecms/page/index.js` 188 | - Most [module configuration settings](https://docs.apostrophecms.org/reference/module-api/module-overview.html#module-configuration) for Admin UI, request routing, and MongoDB interaction 189 | 190 | **Key differences:** 191 | - **No frontend code in modules** - Stylesheets, templates (implemented as Astro components), and client-side JavaScript go in the Astro project instead 192 | - **No template helpers** - Skip `helper()`, `extendHelpers()`, `components()`, and `renderRoutes()` functions 193 | 194 | The `modules/@apostrophecms/home-page` module loads the core `views/layout.html` file, which has been modified to indicate that editing should take place in the Astro frontend. 195 | 196 | ### For Astro Developers 197 | 198 | The Astro portion follows standard conventions with components in `src` and assets in `public`. Configuration is managed through `astro.config.mjs` following standard practices. 199 | 200 | **What stays the same:** 201 | - Standard Astro project organization 202 | - Normal component and template patterns 203 | - Client-side asset management 204 | 205 | **Key differences:** 206 | - **Single route system** - Instead of multiple routes in `pages`, there's one `[...slug].astro` file that handles all routing 207 | - **Template mapping** - Pages map to templates in the `templates` folder, mapped by the `index.js` file in that folder. Each template corresponds to an ApostropheCMS page type, including `index.html` and `show.html` piece-page types 208 | - **Widget system** - The `widgets` folder contains templates for ApostropheCMS widgets, mapped through an `index.js` file in that folder. 209 | - **Required configuration** - The `apostrophe` integration and `output: 'server'` settings must remain for backend integration 210 | 211 | Content is populated by data from the CMS backend and inserted into slots in the main `[...slug].astro` file. Widget data is handled through the mapped templates and added to page templates using the `AposArea` helper component. 212 | 213 | Read more in the [`apostrophe-astro` documentation](https://github.com/apostrophecms/apostrophe-astro) or in the [Apollo tutorial series](https://docs.apostrophecms.org/tutorials/astro/apostrophecms-and-astro.html). 214 | 215 | --- 216 | 217 | ## 🖼️ Image Helper Functions 218 | 219 | ### Overview 220 | These helper functions are designed to work with images in your Astro frontend that come from ApostropheCMS through relationships or attachment fields. If you're using the image widget within an area, you should use the `AposArea` helper instead - these utilities are specifically for handling images that are part of your content model. 221 | 222 | **Important:** These helpers expect a single attachment object, not an array. When working with relationships or array fields, make sure to pass a single image object (e.g., `page.relationship._image[0]`) rather than the full array. 223 | 224 | ### Working with Image Relationships 225 | When you have a relationship field to `@apostrophecms/image` in your content type, you'll typically need to: 226 | 1. Get the image URL (potentially at different sizes for responsive images) 227 | 2. Handle focal points if configured 228 | 3. Get the image dimensions including any cropping that should be applied 229 | 4. Set up proper alt text 230 | 231 | Here's a typical example: 232 | ```js 233 | --- 234 | import { 235 | getAttachmentUrl, 236 | getAttachmentSrcset, 237 | getFocalPoint, 238 | getWidth, 239 | getHeight 240 | } from '../lib/attachments.js'; 241 | 242 | // Get first image from relationship 243 | const image = relationshipField._image[0]; 244 | --- 245 | 246 | {image.alt 255 | ``` 256 | 257 | ### Working with Direct Attachments 258 | For attachment fields (like logo fields), the pattern is similar: 259 | 260 | ```js 261 | Logo 267 | ``` 268 | 269 | ### Image Cropping and Sizes 270 | 271 | **Automatic Crop Handling** 272 | 273 | If you set a crop region for an image in the ApostropheCMS Admin UI, all the helper methods will automatically respect that crop. You don't need to do anything special in your code - the cropped version will be used when generating URLs and srcsets. 274 | 275 | **Size Variants** 276 | 277 | The default size variants are: 278 | - `one-sixth` (190×350px) 279 | - `one-third` (380×700px) 280 | - `one-half` (570×700px) 281 | - `two-thirds` (760×760px) 282 | - `full` (1140×1140px) 283 | - `max` (1600×1600px) 284 | 285 | These sizes will be used to generate the srcset and can be selected by name for the `getAttachmentUrl()` method: 286 | 287 | ``` 288 | getAttachmentUrl(image, { size: 'full' }) 289 | ``` 290 | 291 | You can use custom size names in both `getAttachmentUrl()` and the srcset options. For example: 292 | ```js 293 | const customUrl = getAttachmentUrl(image, { size: 'custom-banner' }); 294 | 295 | // Custom srcset configuration 296 | const srcset = getAttachmentSrcset(image, { 297 | sizes: [ 298 | { name: 'small', width: 300 }, 299 | { name: 'medium', width: 600 }, 300 | { name: 'large', width: 900 }, 301 | ] 302 | }); 303 | ``` 304 | 305 | > Important: These helpers don't generate the image sizes - they just reference sizes that already exist. To use custom sizes, you must configure the [`@apostrophecms/attachment` module](https://docs.apostrophecms.org/reference/modules/attachment.html#configuration) to create those sizes when images are uploaded. You can do this in your backend configuration: 306 | 307 | ```javascript 308 | // modules/@apostrophecms/attachment/index.js 309 | module.exports = { 310 | options: { 311 | // Define what sizes should be created on upload 312 | imageSizes: { 313 | 'custom-banner': { width: 1200, height: 400 }, 314 | 'square-thumb': { width: 300, height: 300 }, 315 | 'small': { width: 300 }, 316 | 'medium': { width: 600 }, 317 | 'large': { width: 900 } 318 | } 319 | } 320 | }; 321 | ``` 322 | 323 | See the [attachment module documentation](https://docs.apostrophecms.org/reference/modules/attachment.html#configuration) for complete configuration options. 324 | 325 | ### Working with Focal Points 326 | When using focal points set in the ApostropheCMS admin UI, you'll need to: 327 | 1. Use `object-position` with the focal point value 328 | 2. Set appropriate Bulma image classes (like `is-fullwidth`) 329 | 330 | ```js 331 |
332 | Image with focal point 340 |
341 | ``` 342 | 343 | The `getFocalPoint()` function returns coordinates in the format "X% Y%" (e.g., "50% 50%" for center). If no focal point is set, it returns the default value (default is "center center"). 344 | 345 | ### Core Functions Reference 346 | Key functions available (see JSDoc comments in source for detailed documentation): 347 | - `getAttachmentUrl(attachmentObject, options?)`: Get URL for an image with optional size (defaults to 'full') 348 | - `getAttachmentSrcset(attachmentObject, options?)`: Generate responsive srcset string 349 | - `getWidth(imageObject)`: Get image width, respecting crops 350 | - `getHeight(imageObject)`: Get image height, respecting crops 351 | - `getFocalPoint(attachmentObject, defaultValue?)`: Get focal point coordinates for styling 352 | 353 | --- 354 | 355 | ## 🚀 Deployment Options 356 | 357 | ### **ApostropheCMS Hosting** (Recommended) 358 | Apostrophe can provide easy hosting for any ApostropheCMS-Astro monorepo with little or no extra configuration. This can be set up for deployment from GitHub or other code repository. 359 | 360 | Apostrophe hosting comes with zero-config deployment with automatic: 361 | - Database provisioning and backups 362 | - SSL certificates 363 | - Asset optimization and delivery 364 | - Security updates and monitoring 365 | - combined logs of both services via our hosting CLI 366 | 367 | *Learn more about [ApostropheCMS hosting](https://apostrophecms.com/hosting) or [contact us](https://apostrophecms.com/contact-us) for enterprise hosting.* 368 | 369 | ### **DIY Deployment** 370 | Since this project uses Astro in server mode (SSR), deployment requires careful consideration: 371 | 372 | Third-party hosting will typically require separate servers for the ApostropheCMS and Astro portions of the repositories. This is the typical pattern seen with other CMS that are used with Astro. You will need to specify whether you want the `backend` ApostropheCMS portion of the repo, or the `frontend` Astro project hosted. How this is accomplished will depend on the provider. 373 | 374 | #### Backend (ApostropheCMS) Deployment 375 | 376 | Your ApostropheCMS backend requires: 377 | - Node.js environment (v22 or later recommended) 378 | - MongoDB database 379 | - Asset storage solution (cloud storage like AWS S3) 380 | 381 | There are several examples of common deployment strategies in our [documentation](https://docs.apostrophecms.org/guide/hosting.html) 382 | 383 | Example deployment steps for a typical provider: 384 | 1. Set up a MongoDB instance (Atlas, DigitalOcean, etc.) 385 | 2. Configure your server with Node.js and PM2 386 | 3. Set up your environment variables: 387 | ```bash 388 | NODE_ENV=production 389 | APOS_MONGODB_URI=YOUR_mongodb_connection_string 390 | APOS_EXTERNAL_FRONT_KEY=a_random_string 391 | APOS_S3_BUCKET=YOUR-bucket-name 392 | APOS_S3_SECRET=YOUR-s3-secret 393 | APOS_S3_KEY=YOUR-s3-key 394 | APOS_S3_REGION=YOUR-chosen-region 395 | ``` 396 | The remainder of the deployment will depend on the hosting platform being used and how that deployment is triggered. Generally, it will comprise a build step followed by bringing up the server. If you are not deploying with Git, you will also need to set the `APOS_RELEASE_ID` to a unique, random value. Again, make sure that you specify that the `backend` folder is to be used as the root for your deployment. 397 | 398 | #### Frontend (Astro) Deployment 399 | 400 | Your Astro frontend can be deployed to any static hosting provider that supports SSR (Server-Side Rendering). Popular options include: 401 | - Netlify 402 | - Vercel 403 | - Cloudflare Pages 404 | - AWS Amplify 405 | There are a number of tutorials in the [Astro documentation](https://docs.astro.build/en/guides/deploy/#deployment-guides) to use as a starting point. The only modifications are the extra environment variable, `APOS_EXTERNAL_FRONT_KEY=a_random_string` set to the same string as your backend project, and to make sure that you are specifying the `frontend` folder as the root of the project. 406 | 407 | #### Netlify Deployment Example 408 | 409 | 1. Log in to your [Netlify](https://www.netlify.com/) account. 410 | 2. Create a new site by connecting your Git repository. 411 | 3. In the "Build settings" configuration: 412 | - **Base directory**: `frontend` 413 | - **Build command**: `npm run build` 414 | - **Publish directory**: `frontend/dist` 415 | 4. Access Site Settings: 416 | -Navigate to the "Site settings" for the selected site. 417 | 5. Scroll down and find the "Environment variables" section under the "Build & deploy" tab. Click "Edit variables". Add a New Variable: 418 | - **Key**: `APOS_EXTERNAL_FRONT_KEY` 419 | - **Value**: `a_random_string` 420 | 6. Save your configuration and deploy the site. 421 | 422 | The build settings can also be supplied through a `netlify.toml` file at the root of your project. 423 | 424 | --- 425 | 426 | ## 🚑 Need Help? 427 | 428 | - **Community Support**: Join our [Discord community](https://discord.com/invite/HwntQpADJr) for help from other developers 429 | - **Professional Support**: Dedicated support packages are available - [Contact us](https://apostrophecms.com/contact-us) to learn more 430 | - **Training**: Professional training and consultation services available 431 | 432 | --- 433 | 434 | ## 📚 Learn More 435 | 436 | - **[ApostropheCMS Documentation](https://docs.apostrophecms.org/)** - Complete CMS guide 437 | - **[Astro Documentation](https://docs.astro.build/)** - Learn more about Astro 438 | - **[Astro + ApostropheCMS Guide](https://docs.astro.build/en/guides/cms/apostrophecms/)** - Integration details 439 | - **[Building a Site Tutorial](https://docs.apostrophecms.org/tutorials/astro/apostrophecms-and-astro.html)** - Building a complete site with the Apollo theme 440 | - **[apostrophe-astro Package](https://github.com/apostrophecms/apostrophe-astro)** - Bridge package docs 441 | 442 | --- 443 | 444 | ## 🎯 Ready to Build Something Amazing? 445 | 446 | This starter kit includes the essentials to get you building: 447 | - ✅ Basic page templates (home, default, blog) 448 | - ✅ Core ApostropheCMS widgets plus layout widget 449 | - ✅ Image optimization helpers 450 | - ✅ Clean, non-opinionated structure 451 | - ✅ Production deployment configuration 452 | - ✅ Development tooling setup 453 | 454 | **A clean foundation** for your project, not a finished product. Perfect for developers who want to start with solid architecture and build their vision on top. 455 | 456 | *Need advanced features like granular permissions, advanced workflows, or premium support? [Explore ApostropheCMS Pro](https://apostrophecms.com/pro) for enterprise-grade capabilities.* 457 | 458 | --- 459 | 460 | *Built with ❤️ by the ApostropheCMS team. [Star us on GitHub](https://github.com/apostrophecms) if this helps your project!* --------------------------------------------------------------------------------