מדריך קלאודפלייר בעברית

תיעוד 239

בניית בלוג באמצעות Nuxt.js ו-Sanity.io ב-Cloudflare Pages

במדריך הזה תבנו יישום בלוג באמצעות Nuxt.js ו-Sanity.io ותפרסמו אותו ב-Cloudflare Pages. Nuxt.js הוא מחולל אתרים סטטיים חזק, שנבנה על מסגרת העבודה Vue.js לחזית. Sanity.io הוא כלי CMS נטול ראש (headless), שנבנה לניהול הנתונים של היישום בלי צורך לתחזק מסד נתונים.

#דרישות מוקדמות

#יצירת פרויקט Sanity חדש

כדי להתחיל, צרו פרויקט Sanity חדש באמצעות אחת מהתבניות של Sanity, תבנית הבלוג. אם תרצו להתאים את התצורה, אפשר לשנות את הסכמה או לבחור תבנית מותאמת אישית.

#התקנת Sanity והגדרת מערך הנתונים

צרו את פרויקט Sanity החדש על ידי התקנת לקוח @sanity/cli מ-npm, והרצת sanity init בטרמינל:

npm

npm i @sanity/cli

yarn

yarn add @sanity/cli

pnpm

pnpm add @sanity/cli

bun

bun add @sanity/cli

npm

npx sanity init

yarn

yarn sanity init

pnpm

pnpm sanity init

כשיוצרים פרויקט Sanity, אפשר לבחור באחת מהסכמות המוגדרות מראש שלהם. סכמות מתארות את הצורה של הנתונים במערך הנתונים של Sanity. אם הייתם מתחילים פרויקט חדש לגמרי, אפשר לבחור לאתחל את הסכמה מאפס, אבל בינתיים בחרו בסכמת Blog.

#בדיקת הסכמה

אחרי שהפרויקט נוצר, אפשר לנווט לתיקייה ולהפעיל את הסטודיו באופן מקומי:

cd my-sanity-project

npm

npx sanity start

yarn

yarn sanity start

pnpm

pnpm sanity start

סטודיו Sanity הוא המקום שבו אפשר ליצור רשומות חדשות למערך הנתונים. כברירת מחדל, הרצת הסטודיו באופן מקומי הופכת אותו לזמין ב-localhost:3333. כנסו לשם עכשיו וצרו את רשומת המחבר. אפשר גם ליצור כאן פוסטים לבלוג.

יצירת פוסט בבלוג בלוח הבקרה של פרויקט Sanity

#פריסת מערך הנתונים

כשאתם מוכנים לפרוס את הסטודיו, הריצו sanity deploy כדי לבחור כתובת URL ייחודית לסטודיו. המשמעות היא שאתם (או כל מי שתזמינו לנהל את הבלוג) יכולים לגשת לסטודיו בדומיין yoururl.sanity.studio.

npm

npx sanity deploy

yarn

yarn sanity deploy

pnpm

pnpm sanity deploy

אחרי שפרסתם את סטודיו Sanity:

  1. היכנסו ללוח הניהול של Sanity (manage.sanity.io ↗︎).
  2. מצאו את הפרויקט.
  3. בחרו ב-API.
  4. הוסיפו את http://localhost:3000 כמקור CORS מותר לפרויקט.

המשמעות היא שבקשות שמגיעות למערך הנתונים של Sanity מיישום Nuxt ייכנסו לרשימת ההיתרים.

הגדרות CORS של פרויקט Sanity

#יצירת פרויקט Nuxt.js חדש

בשלב הבא, צרו פרויקט Nuxt.js. בטרמינל חדש, השתמשו ב-create-nuxt-app כדי להגדיר פרויקט Nuxt חדש:

npm

npx create-nuxt-app blog

yarn

yarn dlx create-nuxt-app blog

pnpm

pnpx create-nuxt-app blog

חשוב לוודא שבוחרים במצב רינדור Universal (SSR / SSG) וביעד פריסה Static (Static/JAMStack hosting), בזמן שעוברים על תהליך ההגדרה.

אחרי שסיימתם את הפרויקט, בצעו cd לתוך הפרויקט החדש, והפעילו שרת פיתוח מקומי באמצעות הרצת yarn dev (או, אם בחרתם ב-npm כמנהל החבילות, npm run dev):

cd blog

npm

npm run dev

yarn

yarn run dev

pnpm

pnpm run dev

#שילוב Sanity.io

אחרי שיישום Nuxt.js מוגדר, הוסיפו את התוסף @sanity/nuxt של Sanity לפרויקט Nuxt:

npm

npm i @nuxtjs/sanity @sanity/client

yarn

yarn add @nuxtjs/sanity @sanity/client

pnpm

pnpm add @nuxtjs/sanity @sanity/client

bun

bun add @nuxtjs/sanity @sanity/client

כדי להגדיר את התוסף ביישום Nuxt.js, תצטרכו לספק כמה פרטי תצורה. הדרך הקלה ביותר לעשות זאת היא להעתיק את תיקיית sanity.json מהסטודיו אל תיקיית היישום (אם כי יש גם שיטות אחרות: עיינו בתיעוד של @nuxt/sanity ↗︎).

Adding sanity.jsonsh

cp ../my-sanity-project/sanity.json .

לבסוף, הוסיפו את @nuxtjs/sanity כ-build module בתצורת Nuxt:

nuxt.config.jsjs

{
	buildModules: ["@nuxtjs/sanity"];
}

#הגדרת רכיבים

כש-Sanity מוגדר ביישום, אפשר להתחיל להשתמש בו כדי לרנדר את הבלוג. עכשיו תגדירו כמה דפים שימשכו נתונים מ-API של Sanity וירנדרו אותם. שימו לב: אם אינכם מכירים את Nuxt, מומלץ לעיין במדריך Nuxt ↗︎, שילמד אתכם כמה מושגי יסוד סביב בניית יישומים עם Nuxt.

#הגדרת דף האינדקס

כדי להתחיל, עדכנו את דף index, שירנדר כשמבקרים בנתיב השורש (/). בקובץ pages/index.vue:

pages/index.vuehtml

<template>
	<div class="container">
		<div>
			<h1 class="title">My Blog</h1>
		</div>
		<div class="posts">
			<div v-for="post in posts" :key="post._id">
				<h2><a v-bind:href="post.slug.current" v-text="post.title" /></h2>
			</div>
		</div>
	</div>
</template>

<script>
	import { groq } from "@nuxtjs/sanity";

	export default {
		async asyncData({ $sanity }) {
			const query = groq`*[_type == "post"]`;
			const posts = await $sanity.fetch(query);
			return { posts };
		},
	};
</script>

<style>
	.container {
		margin: 2rem;
		min-height: 100vh;
	}
	.posts {
		margin: 2rem 0;
	}
</style>

Vue SFCs, או single file components, הם תכונה ייחודית של Vue שמאפשרת לשלב JavaScript, HTML ו-CSS בקובץ אחד. ב-pages/index.vue מסופקת תגית template, שמייצגת את רכיב Vue.

חשוב: v-for משמש כהנחיה שאומרת ל-Vue לרנדר HTML עבור כל post במערך של posts:

Inspecting the v-for directivehtml

<div v-for="post in posts" :key="post._id">
	<h2><a v-bind:href="post.slug.current" v-text="post.title" /></h2>
</div>

כדי למלא את מערך posts הזה, משתמשים בפונקציה asyncData, ש-Nuxt מספק כדי לבצע קריאות אסינכרוניות (לדוגמה, בקשות רשת) ולמלא את נתוני הדף.

האובייקט $sanity מסופק על ידי השילוב של Nuxt ו-Sanity.js כאמצעי לבצע בקשות למערך הנתונים של Sanity. באמצעות קריאה ל-$sanity.fetch והעברת שאילתה, אפשר לאחזר נתונים ספציפיים ממערך הנתונים של Sanity, ולהחזיר אותם כנתוני הדף.

אם לא השתמשתם ב-Sanity בעבר, כנראה שאינכם מכירים את GROQ, שפת השאילתות GRaph Oriented Query language ש-Sanity מספק לממשק עם מערך הנתונים. GROQ היא שפה חזקה שמאפשרת לומר ל-API של Sanity אילו נתונים רוצים להוציא ממערך הנתונים. בשאילתה הראשונה, תאמרו ל-Sanity לאחזר כל אובייקט במערך הנתונים עם ערך _type של post:

A basic GROQ queryjs

const query = groq`*[_type == "post"]`;
const posts = await $sanity.fetch(query);

#הגדרת דף פוסט הבלוג

דף index שלנו מרנדר קישור לכל פוסט בלוג במערך הנתונים, ומשתמש בערך slug כדי לקבוע את כתובת ה-URL של פוסט. לדוגמה, אם אני יוצר פוסט בלוג בשם "Hello World" ומגדיר את ה-slug ל-hello-world, יישום Nuxt אמור להיות מסוגל לטפל בבקשה לדף /hello-world, ולאחזר את פוסט הבלוג המתאים מ-Sanity.

ל-Nuxt יש תמיכה מובנית בדפים מהסוג הזה, על ידי יצירת קובץ חדש ב-pages בפורמט _slug.vue. בפונקציה asyncData של הדף, אפשר אחר כך להשתמש בארגומנט params כדי להפנות אל ה-slug:

pages/_slug.vuehtml

<script>
	export default {
		async asyncData({ params, $sanity }) {
			console.log(params); // { slug: "hello-world" }
		},
	};
</script>

עם זה בראש, אפשר לבנות את pages/_slug.vue כך שייקח את ערך slug הנכנס, יבצע שאילתה ל-Sanity כדי למצוא את פוסט הבלוג התואם, וירנדר את כותרת post של פוסט הבלוג:

pages/_slug.vuehtml

<template>
	<div class="container">
		<div v-if="post">
			<h1 class="title" v-text="post.title" />
			<div class="content"></div>
		</div>
		<h4><a href="/">← Go back</a></h4>
	</div>
</template>

<script>
	import { groq } from "@nuxtjs/sanity";

	export default {
		async asyncData({ params, $sanity }) {
			const query = groq`*[_type == "post" && slug.current == "${params.slug}"][0]`;
			const post = await $sanity.fetch(query);
			return { post };
		},
	};
</script>

<style>
	.container {
		margin: 2rem;
		min-height: 100vh;
	}

	.content {
		margin: 2rem 0;
		max-width: 38rem;
	}

	p {
		margin: 1rem 0;
	}
</style>

בביקור, לדוגמה, ב-/hello-world, Nuxt ייקח את ה-slug הנכנס hello-world, ויבצע שאילתת GROQ ל-Sanity עבור כל אובייקט עם _type של post, וגם slug שתואם לערך /hello-world. מתוך הקבוצה הזו אפשר לקבל את האובייקט הראשון במערך (באמצעות אופרטור אינדקס המערך שמוצאים ב-JavaScript: [0]) ולהגדיר אותו כ-post בנתוני הדף.

#רינדור תוכן לפוסט בלוג

רינדרתם את כותרת post של הבלוג, אבל עדיין חסר התוכן של פוסט הבלוג עצמו. כדי לרנדר אותו, ייבאו את החבילה sanity-blocks-vue-component ↗︎, שלוקחת את פורמט Portable Text ↗︎ של Sanity ומרנדרת אותו כרכיב Vue.

קודם, התקינו את חבילת npm:

npm

npm i sanity-blocks-vue-component

yarn

yarn add sanity-blocks-vue-component

pnpm

pnpm add sanity-blocks-vue-component

bun

bun add sanity-blocks-vue-component

אחרי שהחבילה מותקנת, צרו את plugins/sanity-blocks.js, שיייבא את הרכיב וירשום אותו כרכיב Vue בשם block-content:

plugins/sanity-blocks.jsjs

import Vue from "vue";
import BlockContent from "sanity-blocks-vue-component";
Vue.component("block-content", BlockContent);

בתצורת Nuxt, nuxt.config.js, ייבאו את הקובץ הזה כחלק מההנחיה plugins:

nuxt.config.jsjs

{
	plugins: ["@/plugins/sanity-blocks.js"];
}

ב-pages/_slug.vue אפשר עכשיו להשתמש ברכיב <block-content> כדי לרנדר את התוכן. זה מקבל את הצורה של רכיב HTML מותאם אישית, ומקבל שלושה ארגומנטים: :blocks, שמציין מה לרנדר (במקרה שלנו, child), v-for, שמקבל איטרטור של המקום שממנו לקבל את child (במקרה שלנו, post.body), ו-:key, שעוזר ל-Vue לעקוב אחרי רינדור מצב ↗︎ באמצעות ערך ייחודי לכל פוסט: כלומר, ערך _id.

pages/_slug.vuehtml

<template>
	<div class="container">
		<div v-if="post">
			<h1 class="title" v-text="post.title" />
			<div class="content">
				<block-content
					:blocks="child"
					v-for="child in post.body"
					:key="child._id"
				/>
			</div>
		</div>
		<h4><a href="/">← Go back</a></h4>
	</div>
</template>

<script>
	import { groq } from "@nuxtjs/sanity";

	export default {
		async asyncData({ params, $sanity }) {
			const query = groq`*[_type == "post" && slug.current == "${params.slug}"][0]`;
			const post = await $sanity.fetch(query);
			return { post };
		},
	};
</script>

<style>
	.container {
		margin: 2rem;
		min-height: 100vh;
	}

	.content {
		margin: 2rem 0;
		max-width: 38rem;
	}

	p {
		margin: 1rem 0;
	}
</style>

ב-pages/index.vue אפשר להשתמש ברכיב block-content כדי לרנדר תקציר של התוכן, על ידי לקיחת הבלוק הראשון בתוכן פוסט הבלוג ורינדור שלו:

pages/index.vuehtml

<template>
	<div class="container">
		<div>
			<h1 class="title">My Blog</h1>
		</div>
		<div class="posts">
			<div v-for="post in posts" :key="post._id">
				<h2><a v-bind:href="post.slug.current" v-text="post.title" /></h2>
				<div class="summary">
					<block-content
						:blocks="post.body[0]"
						v-bind:key="post.body[0]._id"
						v-if="post.body.length"
					/>
				</div>
			</div>
		</div>
	</div>
</template>

<script>
	import { groq } from "@nuxtjs/sanity";

	export default {
		async asyncData({ $sanity }) {
			const query = groq`*[_type == "post"]`;
			const posts = await $sanity.fetch(query);
			return { posts };
		},
	};
</script>

<style>
	.container {
		margin: 2rem;
		min-height: 100vh;
	}
	.posts {
		margin: 2rem 0;
	}
	.summary {
		margin-top: 0.5rem;
	}
</style>

יש עוד דברים רבים בתוך סכמת הבלוג שאפשר להוסיף לפרויקט. כתרגיל, שקלו אחד מהבאים כדי להמשיך לפתח את ההבנה של איך בונים עם CMS נטול ראש:

  • צרו את pages/authors.vue, ורנדרו רשימת מחברים (בדומה ל-pages/index.vue, אבל עבור אובייקטים עם _type == "author")
  • קראו את תיעוד Sanity על שימוש בהפניות ב-GROQ ↗︎, והשתמשו בו כדי לרנדר מידע על המחבר בדף פוסט בלוג

#פרסום עם Cloudflare Pages

פרסום הפרויקט עם Cloudflare Pages הוא תהליך בן שני שלבים: קודם, דחפו את הפרויקט ל-GitHub, ואחר כך, בלוח הבקרה של Cloudflare Pages, הגדירו פרויקט חדש על בסיס מאגר GitHub הזה. Pages יפרוס גרסה חדשה של האתר בכל פעם שתפרסמו, ואפילו יגדיר פריסות תצוגה מקדימה בכל פעם שתפתחו בקשת משיכה חדשה.

כדי לדחוף את הפרויקט ל-GitHub, צרו מאגר חדש ↗︎, ופעלו לפי ההוראות כדי לדחוף את מאגר Git המקומי ל-GitHub.

אחרי שדחפתם את הפרויקט ל-GitHub, פרסו את האתר ל-Pages:

  1. בלוח הבקרה של Cloudflare, עברו לדף Workers & Pages. עברו אל Workers & Pages ↗
  2. בחרו ב-Create application > Pages > Import an existing Git repository.
  3. בחרו במאגר GitHub החדש שיצרתם ובחרו ב-Begin setup.
  4. במקטע Set up builds and deployments, תחת Build settings > Framework preset, בחרו ב-Nuxt. Pages יגדיר עבורכם את השדות הנכונים באופן אוטומטי.

כשהאתר נפרס, תקבלו כתובת URL ייחודית לצפייה בו בסביבת הייצור.

כדי לפרוס את הפרויקט באופן אוטומטי כשהנתונים ב-Sanity.io משתנים, אפשר להשתמש ב-Deploy Hooks. צרו כתובת URL חדשה של Deploy Hook ב-Pages project > Settings. בדף Settings של פרויקט Sanity, מצאו את המקטע Webhooks, והוסיפו את כתובת ה-URL של Deploy Hook, כפי שרואים למטה:

הוספת כתובת URL של Deploy Hook בלוח הבקרה של Sanity

עכשיו, כשמבצעים שינוי במערך הנתונים של Sanity.io, Sanity יבצע בקשה לכתובת ה-URL הייחודית של Deploy Hook, וזה יתחיל פריסת Cloudflare Pages חדשה. בדרך הזו, יישום Pages יישאר מעודכן כשמוסיפים פוסטי בלוג חדשים, או עורכים פוסטים קיימים.

#סיכום

על ידי השלמת המדריך הזה, פרסתם בהצלחה בלוג משלכם, שמופעל על ידי Nuxt, Sanity.io ו-Cloudflare Pages. אפשר למצוא את קוד המקור של שני בסיסי הקוד ב-GitHub:

אם נהניתם מהמדריך הזה, ייתכן שתתעניינו ללמוד איך אפשר להשתמש ב-Cloudflare Workers, פלטפורמת הפונקציות נטולת השרת החזקה שלנו, כדי להרחיב את האתר הקיים. עיינו במדריך Build an API for your front end using Pages Functions כדי ללמוד עוד.