From 7cb511d2abbcbafd3ddc1ae6e1c7ba0bb5c6ad1e Mon Sep 17 00:00:00 2001 From: Elod <33983644+Elod-T@users.noreply.github.com> Date: Fri, 9 Jun 2023 16:45:11 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Added=20Astro=20Docs=20for=20Strapi?= =?UTF-8?q?=20CMS=20integration=20=F0=9F=93=9A=20(#3373)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Added Astro Docs for Strapi CMS integration 📚 🌐 Unlock a world of possibilities for your web development projects by harnessing the power of Astro and the flexibility of Strapi CMS. 🚀 * ✨ Finalize docs and do final touches 🎉 * 📁 Added logo file and config for Strapi * ✨ Enhance style and content * Sarah text editing * Fix import paths --------- Co-authored-by: Sarah Rainsberger --- public/logos/strapi.svg | 8 + src/content/docs/en/guides/cms/strapi.mdx | 425 ++++++++++++++++++++++ src/data/logos.ts | 1 + 3 files changed, 434 insertions(+) create mode 100644 public/logos/strapi.svg create mode 100644 src/content/docs/en/guides/cms/strapi.mdx diff --git a/public/logos/strapi.svg b/public/logos/strapi.svg new file mode 100644 index 0000000000000..58b47d87dc046 --- /dev/null +++ b/public/logos/strapi.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/content/docs/en/guides/cms/strapi.mdx b/src/content/docs/en/guides/cms/strapi.mdx new file mode 100644 index 0000000000000..dd1e7ddeb2682 --- /dev/null +++ b/src/content/docs/en/guides/cms/strapi.mdx @@ -0,0 +1,425 @@ +--- +title: Strapi & Astro +description: Add content to your Astro project using Strapi Headless CMS +type: cms +service: Strapi +stub: false +i18nReady: true +--- + +import FileTree from '~/components/FileTree.astro'; +import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; + +[Strapi](https://strapi.io/) is an open-source, customizable, headless CMS. + +## Integrating with Astro + +This guide will build a wrapper function to connect Strapi with Astro. + +### Prerequisites + +To get started, you will need to have the following: + +1. **An Astro project** - If you don't have an Astro project yet, our [Installation guide](/en/install/auto/) will get you up and running in no time. +2. **A Strapi CMS server** - You can [set up a Strapi server on a local environment](https://docs.strapi.io/dev-docs/quick-start). + +### Adding the Strapi URL in `.env` + +To add your Strapi URL to Astro, create a `.env` file in the root of your project (if one does not already exist) and add the following variable: + +```ini title=".env" +STRAPI_URL="http://127.0.0.1:1337" // or use your IP address +``` + +Now, you can use this environment variable in your Astro project. + +If you would like to have IntelliSense for your environment variable, you can create a `env.d.ts` file in the `src/` directory and configure `ImportMetaEnv` like this: + +```ts title="src/env.d.ts" +interface ImportMetaEnv { + readonly STRAPI_URL: string; +} +``` + +Your root directory should now include the new file(s): + + + - src/ + - **env.d.ts** + - **.env** + - astro.config.mjs + - package.json + + +### Creating the API wrapper + +Create a new file at `lib/strapi.ts` and add the following wrapper function to interact with the Strapi API: + +```ts title="lib/strapi.ts" +interface Props { + endpoint: string; + query?: Record; + wrappedByKey?: string; + wrappedByList?: boolean; +} + +/** + * Fetches data from the Strapi API + * @param endpoint - The endpoint to fetch from + * @param query - The query parameters to add to the url + * @param wrappedByKey - The key to unwrap the response from + * @param wrappedByList - If the response is a list, unwrap it + * @returns + */ +export default async function fetchApi({ + endpoint, + query, + wrappedByKey, + wrappedByList, +}: Props): Promise { + if (endpoint.startsWith('/')) { + endpoint = endpoint.slice(1); + } + + const url = new URL(`${import.meta.env.STRAPI_URL}/api/${endpoint}`); + + if (query) { + Object.entries(query).forEach(([key, value]) => { + url.searchParams.append(key, value); + }); + } + const res = await fetch(url.toString()); + let data = await res.json(); + + if (wrappedByKey) { + data = data[wrappedByKey]; + } + + if (wrappedByList) { + data = data[0]; + } + + return data as T; +} +``` + +This function requires an object with the following properties: + +1. `endpoint` - The enpoint you are fetching. +2. `query` - The query parameters to add to the end of URL +3. `wrappedByKey` - The `data` key in the object that wraps your `Response`. +4. `wrappedByList` - A parameter to "unwrap" the list returned by Strapi, and return only the first item. + +### Optional: Creating the Article interface + +If you are using TypeScript, create the following Article interface to correspond to the Strapi content types at `src/interfaces/article.ts`: + +```ts title="src/interfaces/article.ts" +export default interface Article { + id: number; + attributes: { + title: string; + description: string; + content: string; + slug: string; + createdAt: string; + updatedAt: string; + publishedAt: string; + }; +} +``` + +:::note +You can modify this interface, or create multiple interfaces, to correspond to your own project data. +::: + + + - src/ + - interfaces/ + - **article.ts** + - lib/ + - strapi.ts + - env.d.ts + - .env + - astro.config.mjs + - package.json + + +### Displaying a list of articles + +1. Update your home page `src/pages/index.astro` to display a list of blog posts, each with a description and a link to its own page. + +2. Import the wrapper function and the interface. Add the following API call to fetch your articles and return a list: + + ```astro title="src/pages/index.astro" + --- + import fetchApi from '../lib/strapi'; + import type Article from '../interfaces/article'; + + const articles = await fetchApi({ + endpoint: 'articles', // the content type to fetch + wrappedByKey: 'data', // the key to unwrap the response + }); + --- + ``` + + The API call requests data from `http://localhost:1337/api/articles` and returns `articles`: an array of json objects representing your data: + + ``` + [ + { + id: 1, + attributes: { + title: "What's inside a Black Hole", + description: 'Maybe the answer is in this article, or not...', + content: "Well, we don't know yet...", + slug: 'what-s-inside-a-black-hole', + createdAt: '2023-05-28T13:19:46.421Z', + updatedAt: '2023-05-28T13:19:46.421Z', + publishedAt: '2023-05-28T13:19:45.826Z' + } + }, + // ... + ] + ``` + +3. Using data from the `articles` array returned by the API, display your Strapi blog posts in a list. These posts will link to their own individual pages, which you will create in the next step. + + ```astro title="src/pages/index.astro" + --- + import fetchApi from '../lib/strapi'; + import type Article from '../interfaces/article'; + + const articles = await fetchApi({ + endpoint: 'articles', + wrappedByKey: 'data', + }); + --- + + + + + Strapi & Astro + + + +
+ +
+ + + ``` + +### Generating article pages + +Create the file `src/pages/blog/[slug].astro` to [dynamically generate a page](/en/core-concepts/routing/#dynamic-routes) for each article. + + + - src/ + - interfaces/ + - article.ts + - lib/ + - strapi.ts + - pages/ + - index.astro + - blog/ + - **[slug].astro** + - env.d.ts + - .env + - astro.config.mjs + - package.json + + +#### Static site generation + +In Astro's default static mode (SSG), use [`getStaticPaths()`](/en/reference/api-reference/#getstaticpaths) to fetch your list of articles from Strapi. + +```astro title="src/pages/blog/[slug].astro +--- +import fetchApi from '../../lib/strapi'; +import type Article from '../../interfaces/article'; + +export async function getStaticPaths() { + const articles = await fetchApi({ + endpoint: 'articles', + wrappedByKey: 'data', + }); + + return articles.map((article) => ({ + params: { slug: article.attributes.slug }, + props: article, + })); +} +type Props = Article; + +const article = Astro.props; +--- +``` + +Create the template for each page using the properties of each post object. + + +```astro title="src/pages/blog/[slug].astro ins={21-43} +--- +import fetchApi from '../../lib/strapi'; +import type Article from '../../interfaces/article'; + +export async function getStaticPaths() { + const articles = await fetchApi({ + endpoint: 'articles', + wrappedByKey: 'data', + }); + + return articles.map((article) => ({ + params: { slug: article.attributes.slug }, + props: article, + })); +} +type Props = Article; + +const article = Astro.props; +--- + + + + + {article.attributes.title} + + + +
+ + +

{article.attributes.title}

+ + +

{article.attributes.content}

+ + + {article.attributes.content} + + + +
+ + +``` +:::tip +Make sure to choose the right rendering for your content. For markdown check out our [markdown guide](/en/guides/markdown-content/). If you are rendering html refer to [this guide](/en/reference/directives-reference/#sethtml) for safety. +::: + +#### Server-side rendering + +If you've [opted into SSR mode](/en/guides/server-side-rendering/#enabling-ssr-in-your-project) with `output: server` or `output: hybrid`, [generate your dynamic routes](/en/core-concepts/routing/#server-ssr-mode) using the following code: + +Create the `src/pages/blog/[slug].astro` file: + +```astro title="src/pages/blog/[slug].astro" +--- +import fetchApi from '../../../lib/strapi'; +import type Article from '../../../interfaces/article'; + +const { slug } = Astro.params; + +let article: Article; + +try { + article = await fetchApi
({ + endpoint: 'articles', + wrappedByKey: 'data', + wrappedByList: true, + query: { + 'filters[slug][$eq]': slug || '', + }, + }); +} catch (error) { + return Astro.redirect('/404'); +} +--- + + + + + {article.attributes.title} + + + +
+ + +

{article.attributes.title}

+ + +

{article.attributes.content}

+ + + {article.attributes.content} + + + +
+ + +``` + +This file will fetch and render the page data from Strapi that matches the dynamic `slug` parameter. + +Since you are using a redirect to `/404`, create a 404 page in `src/pages`: + +```astro title="src/pages/404.astro" + + + Not found + + +

Sorry, this page does not exist.

+ + + +``` + +If the article is not found, the user will be redirected to this 404 page and be greeted by a lovely cat. + +### Publishing your site + +To deploy your website, visit our [deployment guides](/en/guides/deploy/) and follow the instructions for your preferred hosting provider. + +#### Rebuild on changes + +If your project is using Astro's default static mode, you will need to set up a webhook to trigger a new build when your content changes. If you are using Netlify or Vercel as your hosting provider, you can use its webhook feature to trigger a new build from Strapi. + +##### Netlify + +To set up a webhook in Netlify: + +1. Go to your site dashboard and click on **Build & deploy**. + +2. Under the **Continuous Deployment** tab, find the **Build hooks** section and click on **Add build hook**. + +3. Provide a name for your webhook and select the branch you want to trigger the build on. Click on **Save** and copy the generated URL. + +##### Vercel + +To set up a webhook in Vercel: + +1. Go to your project dashboard and click on **Settings**. + +2. Under the **Git** tab, find the **Deploy Hooks** section. + +3. Provide a name for your webhook and the branch you want to trigger the build on. Click **Add** and copy the generated URL. + +##### Adding a webhook to Strapi + +Follow [the Strapi webhooks guide](https://strapi.io/blog/webhooks) to create a webhook in your Strapi admin panel. + +## Official Resources + +- [Strapi Blog Guide For React](https://strapi.io/blog/build-a-blog-with-next-react-js-strapi) by Strapi diff --git a/src/data/logos.ts b/src/data/logos.ts index 1474efd65bd1b..4e7152346323d 100644 --- a/src/data/logos.ts +++ b/src/data/logos.ts @@ -73,6 +73,7 @@ export const logos = LogoCheck({ 'frontmatter-cms': { file: 'frontmatter-cms.svg', padding: '.25em' }, statamic: { file: 'statamic.svg', padding: '.2em' }, xata: { file: 'xata.svg', padding: '0.234em 0.234em 0.1875em' }, + strapi: { file: 'strapi.svg', padding: '.25em' }, }); export type LogoKey = keyof typeof logos;