В этом уроке вы узнаете, как создать приложение с советами по продуктивности с помощью Render, Remix и Strapi.
Приложение для повышения продуктивности будет иметь следующее:
- Целевая страница с описанием приложения,
- страница
/tips
для просмотра всех советов по продуктивности в базе данных, и - Страница, на которой отображаются другие подробности совета по продуктивности.
Веб-приложение “Советы по продуктивности” – это приложение, которое отображает список советов по повышению продуктивности. Это приложение работает путем передачи советов и информации о них из базы данных приложения во фронтенд.
Вы можете получить готовый проект в репозитории Strapi и репозитории Remix.
- Необходимые условия
- Почему Remix?
- Почему Strapi?
- Почему Render?
- Приложение Strapi
- Приложение Remix
- Шаг 1: Инициализация приложения Remix
- Шаг 2: Добавление стиля
- Шаг 3: Редактирование содержимого [Добавить советы].
- 1. Добавьте параграф с описанием приложения:
- 2. Добавьте ссылку на /tips.
- Шаг 4: Создание новых файлов
- Шаг 5: Определите динамические маршруты и стиль
- Развертывание для рендеринга
- Заключение
Необходимые условия
Для выполнения этой статьи вам понадобятся следующие знания:
- JavaScript
- Основы Remix
- Основы Strapi
Вам также необходимо установить следующее:
- Git
- NodeJS
- Yarn
- Npm
Почему Remix?
Remix – это веб-фреймворк, который позволяет вам сосредоточиться на пользовательском интерфейсе, заботясь о скорости и отзывчивости вашего приложения. Remix использует вложенную маршрутизацию, что способствует эффективному взаимодействию между фронтендом и одним или несколькими бэкендами.
Приложение “Советы по продуктивности” состоит из фронтенда, использующего Remix, и бэкенда, использующего Strapi. Эффективное взаимодействие между частями приложения приводит к улучшению и ускорению работы пользователей.
Почему Strapi?
Strapi – это безголовая CMS с открытым исходным кодом, которая позволяет создавать GraphQL и RESTful API. Безголовая CMS позволяет разработчикам легко создавать API, которые могут быть интегрированы с любым фронтендом.
CMS (система управления содержимым) – это программное обеспечение, которое позволяет пользователям создавать и поддерживать содержимое веб-приложения. Безголовая CMS похожа на обычную CMS, но у нее нет фронтенда, с которым взаимодействуют люди.
С помощью Strapi вы можете создать API, который будет следующим:
- Легко интегрируется с любым фронтендом,
- Настраиваемым,
- простым в обслуживании, и
- Легко разрабатывается.
Почему Render?
Render – это облачный сервис, который позволяет развертывать и запускать веб-сайты, приложения и многое другое. С помощью Render вы можете настроить и развернуть весь свой проект с помощью одного файла конфигурации. Этот конфигурационный файл служит образцом для настройки вашего проекта.
Чтобы запустить веб-сайт Remix и приложение Strapi, необходимо развернуть их на облаке. После развертывания приложений вы можете получить к ним доступ, используя доменное имя, предоставленное облачным провайдером, или добавить собственное доменное имя.
Приложение Strapi
Прежде чем начать, убедитесь, что у вас есть аккаунт на GitHub, а затем выполните следующие шаги:
- Откройте этот репозиторий в браузере.
- Нажмите кнопку Использовать этот шаблон, чтобы скопировать репозиторий в свой аккаунт.
- Нажмите кнопку Создать репозиторий из шаблона после заполнения любого имени в качестве имени репозитория (вам не нужно включать все ветки).
- Клонируйте копию репозитория на локальную машину.
- Перейдите к локальному репозиторию в терминале.
- Запустите
yarn install
для установки зависимостей. - Выполните команду
cp .env.example .env
, чтобы скопировать переменные окружения из.env.example
в.env
. - Запустите
yarn develop
, чтобы запустить приложение strapi в режиме разработки.
После настройки и запуска приложения Strapi вам необходимо выполнить несколько действий:
- Откройте страницу http://localhost:1337/admin в браузере.
- Создайте учетную запись администратора на странице администратора.
В приложении Strapi из репозитория уже настроен тип содержимого совета по продуктивности. Вы можете увидеть его, перейдя в Content Type Builder -> Совет.
Прежде чем использовать API, необходимо добавить примеры продуктивных советов:
- Перейдите в Менеджер контента.
- Нажмите кнопку Создать новую запись.
- Заполните поля и выберите автора.
- Нажмите Сохранить.
При желании вы можете добавить больше советов в свое приложение, прежде чем продолжить.
Теперь вы закончили настройку приложения Strapi. Вы можете приступить к созданию фронтенда.
Приложение Remix
Чтобы создать приложение remix, выполните следующие шаги:
- Создайте форк этого репозитория на своем аккаунте GitHub.
- Клонируйте форкнутый репозиторий на свою локальную машину.
- Запустите
npm install
для установки его зависимостей.
Этот репозиторий разделен на пять (5) ветвей, каждая из которых показывает шаги, предпринятые для создания приложения.
Шаг 1: Инициализация приложения Remix
Ссылка на этот шаг доступна здесь. В этой ветке приложение remix было создано и инициализировано. Когда вы запускаете npm run dev
в этой ветке, веб-приложение выглядит так, как показано ниже:
Шаг 2: Добавление стиля
Ссылка на этот шаг доступна здесь.
Чтобы перейти в эту ветку, используйте git checkout step-2
. Изменения, внесенные в этом шаге, касались файла app/root.tsx
:
1. Импортируйте библиотеку Pico CSS.
import {
Link,
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "remix";
export function links () {
return [{
rel: "stylesheet",
href: "https://unpkg.com/@picocss/pico@latest/css/pico.min.css"
}]
}
export function meta() {
return { title: "Productivity Tips" };
}
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<nav style={{marginLeft: 10}}>
<h1>
<Link to="/" style={{color: "var(--h1-color)"}}>
Productivity Tips
</Link>
</h1>
</nav>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
2. Изменение заголовка страницы
import {
Link,
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "remix";
export function links () {
return [{
rel: "stylesheet",
href: "https://unpkg.com/@picocss/pico@latest/css/pico.min.css"
}]
}
export function meta() {
return { title: "Productivity Tips" };
}
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<nav style={{marginLeft: 10}}>
<h1>
<Link to="/" style={{color: "var(--h1-color)"}}>
Productivity Tips
</Link>
</h1>
</nav>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
3. Добавить заголовок “Советы по продуктивности” и связать его с /
import {
Link,
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "remix";
export function links () {
return [{
rel: "stylesheet",
href: "https://unpkg.com/@picocss/pico@latest/css/pico.min.css"
}]
}
export function meta() {
return { title: "Productivity Tips" };
}
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<nav style={{marginLeft: 10}}>
<h1>
<Link to="/" style={{color: "var(--h1-color)"}}>
Productivity Tips
</Link>
</h1>
</nav>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
Когда вы запустите эту программу, веб-приложение будет выглядеть следующим образом:
Шаг 3: Редактирование содержимого [Добавить советы].
Ссылку на этот шаг можно найти здесь.
Для перехода на эту ветку используйте команду git checkout step-3
.
Файл, измененный в этом шаге, – src/index.tsx
, и в нем были сделаны следующие изменения:
1. Добавьте параграф с описанием приложения:
import { Link } from "remix";
export default function Index() {
return (
<main className="container">
<p>
Over time everyone develops a Swiss army knife of tips, tricks,
and hacks to boost productivity. At Render, I created a
#productivity-tips Slack channel for anyone to share their best
productivity boosters with everyone on the team. Using
<a href="https://strapi.io">Strapi</a> and
<a href="https://remix.run">Remix</a>, we made a little web app to
catalog all of these tips and share them with others. 🤓
</p>
<Link to="/tips">👉 Productivity Tips</Link>
</main>
);
}
2. Добавьте ссылку на /tips
.
import { Link } from "remix";
export default function Index() {
return (
<main className="container">
<p>
Over time everyone develops a Swiss army knife of tips, tricks,
and hacks to boost productivity. At Render, I created a
#productivity-tips Slack channel for anyone to share their best
productivity boosters with everyone on the team. Using
<a href="https://strapi.io">Strapi</a> and
<a href="https://remix.run">Remix</a>, we made a little web app to
catalog all of these tips and share them with others. 🤓
</p>
<Link to="/tips">👉 Productivity Tips</Link>
</main>
);
}
/tips
– это страница, которая будет создана на следующем шаге. Эта страница содержит список всех советов, хранящихся в бэкенде Strapi.
Когда вы запустите программу на этом шаге, веб-приложение будет выглядеть так, как показано ниже:
Шаг 4: Создание новых файлов
Ссылку на этот шаг можно найти здесь
Ниже перечислены новые файлы, созданные на этом шаге:
import { Outlet } from "remix";
export default function TipsRoute() {
return (
<main className="container">
<Outlet />
</main>
);
}
import { Link, useLoaderData } from "remix";
import { checkStatus, checkEnvVars } from "~/utils/errorHandling";
export async function loader () {
checkEnvVars();
const res = await fetch(`${process.env.STRAPI_URL_BASE}/api/tips?populate=*`, {
method: "GET",
headers: {
"Authorization": `Bearer ${process.env.STRAPI_API_TOKEN}`,
"Content-Type": "application/json"
}
});
// Handle HTTP response code < 200 or >= 300
checkStatus(res);
const data = await res.json();
// Did Strapi return an error object in its response?
if (data.error) {
console.log('Error', data.error)
throw new Response("Error getting data from Strapi", { status: 500 })
}
return data.data;
}
export default function Tips() {
const tips = useLoaderData();
return (
<ul>
{tips.map((tip) => (
<li key={tip.attributes.Slug}>
<Link to={tip.attributes.Slug}>{tip.attributes.Name}</Link>
</li>
))}
</ul>
);
}
// Custom error class for errors from Strapi API
class APIResponseError extends Error {
constructor(response) {
super(`API Error Response: ${response.status} ${response.statusText}`);
}
}
export const checkStatus = (response) => {
if (response.ok) {
// response.status >= 200 && response.status < 300
return response;
} else {
throw new APIResponseError(response);
}
}
class MissingEnvironmentVariable extends Error {
constructor(name) {
super(`Missing Environment Variable: The ${name} environment variable must be defined`);
}
}
export const checkEnvVars = () => {
const envVars = [
'STRAPI_URL_BASE',
'STRAPI_API_TOKEN'
];
for (const envVar of envVars) {
if (! process.env[envVar]) {
throw new MissingEnvironmentVariable(envVar)
}
}
}
В файле index.jsx
функция useLoaderData()
вызывает функцию loader
в строках 4-27. Функция loader
полезна для отделения программы, взаимодействующей с API, от представления.
import { Link, useLoaderData } from "remix";
import { checkStatus, checkEnvVars } from "~/utils/errorHandling";
export async function loader () {
checkEnvVars();
const res = await fetch(`${process.env.STRAPI_URL_BASE}/api/tips?populate=*`, {
method: "GET",
headers: {
"Authorization": `Bearer ${process.env.STRAPI_API_TOKEN}`,
"Content-Type": "application/json"
}
});
// Handle HTTP response code < 200 or >= 300
checkStatus(res);
const data = await res.json();
// Did Strapi return an error object in its response?
if (data.error) {
console.log('Error', data.error)
throw new Response("Error getting data from Strapi", { status: 500 })
}
return data.data;
}
export default function Tips() {
const tips = useLoaderData();
return (
<ul>
{tips.map((tip) => (
<li key={tip.attributes.Slug}>
<Link to={tip.attributes.Slug}>{tip.attributes.Name}</Link>
</li>
))}
</ul>
);
}
Перед запуском приложения необходимо выполнить следующие шаги:
- Откройте приборную панель Strapi.
- Перейдите в раздел Настройки –> Токены API –> Создать новый API, чтобы создать токен API.
- Нажмите кнопку Сохранить после заполнения необходимых полей.
- Скопируйте созданный API-токен.
- Скопируйте новое содержимое файла
.env.example
в.env
.
STRAPI_URL_BASE=http://localhost:1337
STRAPI_API_TOKEN=a-secret-token-from-the-strapi-admin-gui
- Замените
a-secret-token-from-the-strapi-admin-gui
на токен API, затем нажмите Save.>Если вы используете windows, вам может понадобиться изменить значениеSTRAPI_URL_BASE
наhttp://127.0.0.1:1337
.
После запуска приложения командой npm run dev
веб-приложение должно выглядеть так, как показано ниже:
Шаг 5: Определите динамические маршруты и стиль
Вот ссылка на этот шаг
В этом шаге создаются следующие файлы:
В файле $tipId.jsx
происходит следующее:
- Импортируйте файл
tip.css
в файл$tipId.jsx
.
import { useLoaderData, Link } from "remix";
import { checkStatus, checkEnvVars } from "~/utils/errorHandling";
import stylesUrl from "~/styles/tip.css";
export function links () {
return [{ rel: "stylesheet", href: stylesUrl }];
}
export function meta ({ data }) {
return {
title: data.attributes.Name
}
}
export async function loader ({ params }) {
...
- Создайте компонент
TipRoute
, который экспортируется по умолчанию.
...
export default function TipRoute() {
const tip = useLoaderData();
return (
<div>
<Link to="/tips" style={{ textDecoration: 'none' }}>← back to list</Link>
<hgroup>
<h2>{tip.attributes.Name}</h2>
<h3>by {tip.attributes.Author.data?.attributes.firstname ?? 'an unknown user'}</h3>
</hgroup>
<p>
{tip.attributes.Description}
</p>
<div className="grid">
{tip.attributes.Screenshots.data.map((s) => (
<div key={s.attributes.hash}>
<img
src={s.attributes.formats.thumbnail.url}
alt={tip.attributes.Name + ' screenshot'}
/>
</div>
))}
</div>
</div>
);
}
- Создайте функцию
loader
для хукаuseLoaderData()
в компоненте TipRoute.
import { useLoaderData, Link } from "remix";
import { checkStatus, checkEnvVars } from "~/utils/errorHandling";
import stylesUrl from "~/styles/tip.css";
export function links () {
return [{ rel: "stylesheet", href: stylesUrl }];
}
export function meta ({ data }) {
return {
title: data.attributes.Name
}
}
export async function loader ({ params }) {
checkEnvVars();
const res = await fetch(`${process.env.STRAPI_URL_BASE}/api/tips`
+ `?populate=*&filters[Slug]=${params.tipId}`, {
method: "GET",
headers: {
"Authorization": `Bearer ${process.env.STRAPI_API_TOKEN}`,
"Content-Type": "application/json"
}
})
// Handle HTTP response code < 200 or >= 300
checkStatus(res);
const data = await res.json();
// Did Strapi return an error object in its response?
if (data.error) {
console.log('Error', data.error)
throw new Response("Error getting data from Strapi", { status: 500 })
}
// Did Strapi return an empty list?
if (!data.data || data.data.length === 0) {
throw new Response("Not Found", { status: 404 });
}
const tip = data.data[0];
// For a Tip with no screenshot, replace API returned null with an empty array
tip.attributes.Screenshots.data = tip.attributes.Screenshots.data ?? [];
// Handle image URL being returned as just a path with no scheme and host.
// When storing media on the filesystem (Strapi's default), media URLs are
// return as only a URL path. When storing media using Cloudinary, as we do
// in production, media URLs are returned as full URLs.
for (const screenshot of tip.attributes.Screenshots.data) {
if (!screenshot.attributes.formats.thumbnail.url.startsWith('http')) {
screenshot.attributes.formats.thumbnail.url = process.env.STRAPI_URL_BASE +
screenshot.attributes.formats.thumbnail.url;
}
}
return tip;
}
...
В функции loader
происходит следующее:
- Используйте функцию
checkEnvVars()
для проверки наличия всех необходимых переменных окружения. - Сделайте запрос к маршруту
http://localhost:1337/api/tips
(URL содержит параметры, которые Strapi использует для уточнения запроса). - Используйте функцию
checkStatus()
, чтобы проверить, что статус http в порядке (от 200 до 299). - Проверьте, не вернул ли strapi ошибку в своем ответе. Иногда Strapi отвечает объектом с ошибкой при наличии http-статуса OK.
- Проверьте, не вернул ли strapi пустой список. Если по id нет ни одного продуктивного совета, Strapi возвращает пустой список.
- Обработка маршрутизации для изображений в совете. Strapi использует локальную файловую систему, когда хранит загруженные файлы, и использует облачный сервер. Когда файл загружается с сервера разработки, Strapi не отвечает URL-адресом файла, а только его локальным путем.
Когда вы запустите приложение на этом этапе, страница http://localhost:3000/tips/tip должна выглядеть так, как показано ниже:
Развертывание для рендеринга
Перед развертыванием проекта в облаке необходимо выполнить следующие шаги:
- Откройте репозиторий
strapiconf2022-workshop-strapi
. - Измените значение
repo
s в файле/render.yaml
на URL вашего удаленного репо strapi и remix (в вашем случае это может бытьhttps://github.com/your-username/strapiconf2022-workshop-strapi
иhttps://github.com/your-username/strapiconf2022-workshop-remix
).
services:
- type: web
name: productivity-tips-api
env: node
plan: free
# Update the following line with your Strapi GitHub repo
repo: https://github.com/render-examples/strapiconf2022-workshop-strapi
branch: main
buildCommand: yarn install && yarn build
startCommand: yarn start
healthCheckPath: /_health
envVars:
- key: NODE_VERSION
value: ~16.13.0
- key: NODE_ENV
value: production
- key: CLOUDINARY_NAME
sync: false
- key: CLOUDINARY_KEY
sync: false
- key: CLOUDINARY_SECRET
sync: false
- key: DATABASE_URL
fromDatabase:
name: strapi
property: connectionString
- key: JWT_SECRET
generateValue: true
- key: ADMIN_JWT_SECRET
generateValue: true
- key: API_TOKEN_SALT
generateValue: true
- key: APP_KEYS
generateValue: true
- type: web
name: productivity-tips-web
env: node
plan: free
# Update the following line with your Remix GitHub repo
repo: https://github.com/render-examples/strapiconf2022-workshop-remix
branch: step-5
buildCommand: npm install && npm run build
startCommand: npm start
envVars:
- key: STRAPI_URL_BASE
fromService:
type: web
name: productivity-tips-api
envVarKey: RENDER_EXTERNAL_URL
databases:
- name: strapi
plan: free # This database will expire 90 days after creation
- Сохраните изменения и отправьте их в удаленный репозиторий.
$ git add render.yaml
$ git commit
$ git add
- Войдите в панель управления Render.
- Нажмите на New и выберите Blueprint.
- Добавьте репозиторий, в котором приложение рендеринга сможет найти файл
render.yaml
. В данном случае это репозиторийstrapiconf2022-workshop-strapi
.
Когда вы выберете репо, вам будет предложено добавить следующие данные:
Service Group Name
. Это уникальное имя, которое используется для идентификации проекта в вашей учетной записи.CLOUDINARY_NAME
.CLOUDINARY_KEY
CLOUDINARY_SECRET
.
Чтобы получить значение для последних трех полей, указанных выше, вам нужно сделать следующее:
- Войдите в приборную панель Cloudinary.
- Нажмите Начать конфигурирование.
-
Скопируйте значения следующих полей:
cloud_name
api_key
-
Вставьте скопированные значения в следующие поля соответственно:
CLOUDINARY_NAME
.CLOUDINARY_KEY
CLOUDINARY_SECRET
.
После заполнения полей нажмите Apply Затем дождитесь, пока Render развернет ваше приложение.
Заключение
После развертывания приложения вы можете увидеть адрес развернутого приложения, перейдя на приборную панель, затем выберите:
После выбора любого из вышеперечисленных пунктов вы увидите доменное имя, которое любой может использовать для доступа к вашему веб-приложению.
Чтобы запустить приложение, вам нужно сделать следующее:
- Перейдите в панель администратора приложения Strapi (по адресу
/admin
). - Перейдите в раздел Настройки –> Токены API –> Создать новый токен API.
- Скопируйте созданный API-токен.
- Откройте службу
productivity-tips-web
. - Перейдите в раздел Окружение и нажмите Добавить переменные окружения
- Установите ключ
STRAPI_API_TOKEN
и вставьте сгенерированный API-токен. - Нажмите на Save Changes для развертывания приложения.
После настройки приложения вы теперь можете добавлять советы по продуктивности в ваше приложение из бэкенда Strapi, и теперь вы можете полноценно использовать веб-приложение.
Чтобы расширить свои знания, обязательно ознакомьтесь со следующими ссылками:
- Remix Docs
- Приложение Jokes – учебник по Remix
- Развертывание приложений Strapi на Render
- Док-ты и интеграции хостинга Render