我是一個懶惰的開發者。但我們不都是這樣嗎?
我記得在哪裡聽說過,開發人員越懶,他們就越好。這可能與不重複任務、共享程式碼,當然還有美味的複製義大利麵有關。
甚至還有為此的特殊產品! (這不是附屬鏈接,我只是覺得它很有趣😂)。
但為什麼我們還是會一遍又一遍地做一些事情呢?例如,更改我們的行銷頁面、橫幅、按鈕顏色等等。為什麼開發人員需要反覆被要求在頁面上重新定位某些元素,僅因為產品經理或設計師認為這樣會更好?看來浪費了很多時間…
這就是為什麼當我得知時我非常興奮建造者大作戰。終於有一個工具可以在許多方面為我提供幫助。
Builder 可用於多種用途:
- 用於電子商務的無頭 CMS 或店面。
- 直觀地建立登陸頁面。
- 建立行銷/公司網站。
- 作為 Shopify 店面。
- 建立行動應用程式。
- 創建和發布部落格。
CMS FTW
首先,Builder 是一個 CMS。什麼是內容管理系統?你可能會問。它代表內容管理系統。
CMS 通常有 2 種類型:
- 託管/傳統 CMS – 像 WordPress 或 Drupal 一樣,是一個整體,透過簡潔的應用程式程式碼庫連接網站的前端和後端。它們包含從內容資料庫一直到表示層的所有內容。
- Headless CMS – 完全不連接前端,透過 API 嚴格處理內容。
當資深開發人員想到 CMS 時,他們通常會想到 WordPress、Joomla 和 Drupal。這些一直是處理內容的成熟解決方案,從儲存資料到呈現網站。然而,新一波的 CMS 主要是資料儲存。其中您只能透過 API 存取您的數據,而您對數據的處理方式取決於開發人員。
Builder採取了不同的方法。不僅允許存取數據,還允許存取視覺效果 – 實際的 UI 元件。開發人員可以決定給予非開發團隊成員多少控制權。
大多數 CMS 允許透過所見即所得(所見即所得)來編輯內容,有時還允許透過拖放介面編輯內容。 Builder 還可以與許多不同的前端堆疊集成,例如 React、Angular、Vue 等。他們實現這一目標的方式是允許開發人員透過他們的 SDK 將元件註冊到 Builder。
查詢資料並使用 Builder Visual CMS
正如我所提到的,Builder 的產品非常靈活。你有不同的方法來實現同一個目標。這完全取決於什麼對您和/或您的團隊有用。
我在將 Builder 整合到我之前的職位所從事的專案中獲得了豐富的經驗。我需要解決幾個問題:
- 讓行銷和設計團隊發布部落格文章,同時保持某些設計的一致性和約束。
- 允許非開發人員編輯常見問題頁面。
- 讓主頁和其他行銷頁面上的不同內容可以輕鬆編輯,無需向開發人員開票。
對我們來說幸運的是,我們的堆疊是一個 Next.js 應用程序,並且 Builder 團隊有很棒的文件和程式碼範例,非常接近我們的需求。話雖這麼說,它很接近,但並不準確……我必須嘗試一些不同的變化,直到找到對我來說足夠有意義的東西。
附註:我確實有機會將我的回饋傳遞給 Builder 的產品經理,我被告知後來這是我的靈感的一部分《建造者藍圖》😃
我必須憑記憶,因為我無法再存取程式碼庫,也無法存取建構器帳戶。然而,我記得,對於問題 1 和問題 2 來說,這都是一個非常直接的方法。
- 在 Builder 上定義部分/資料/頁面模型(在我的例子中,模型包括:作者、標題、圖像、描述、slug、createdAt 等)
- 透過 API 金鑰將 Builder 整合到應用程式。
- 在需要資料的頁面上初始化Builder。
- 透過 Builder API 查詢並取得您的資料。
- 使用
BuilderComponent
取得 Builder Visual CMS 中的內容。
對於部落格的一般索引頁面,看起來像這樣(請參閱建構器文檔):
// pages/blog/index.tsx
import { builder } from '@builder.io/react';
import { Header } from '~components/header';
import { Footer } from '~components/footer';
import { Post } from '~components/post';
builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY);
// If you are unfamiliar with Next.js this method is what allows you to get data
// for static pages at build time.
// Here we get our data model "blog-article" from builder, but we don't include
// all the content blocks, as we only need surface data.
export const getStaticProps = async ({ req, res, query }) => {
const page = Number(query?.page) || 0;
const articles = await builder.getAll('blog-article', {
req,
res,
// Include references, like our `author` ref
options: { includeRefs: true },
// For performance, don't pull the `blocks` (the full blog entry content)
// when listing
omit: 'data.blocks',
limit: articlesPerPage,
offset: page * articlesPerPage,
});
return { props: { articles }, revalidate: 5 };
};
//
const Blog = ({ articles }) => {
return (
<>
<Header />
<main>
{articles.map((article) => {
return (
<Post
key={article.id}
slug={article?.data.slug}
title={article?.data?.title}
imagePath={article?.data?.image}
author={{
name: article?.data?.author?.value?.data?.fullName,
}}
tags={article?.data?.tags?.map(({ tag }) => tag)}
createdAt={article?.data?.date}
>
{article?.data?.description}
</Post>
);
})}
</main>
<Footer />
</>
);
};
export default Blog;
現在,讓我們來看看部落格頁面的樣子:
// pages/blog/[slug].tsx
...
builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY);
// we use Next.js's dynamic params to get the post slug and query builder
// one slug at a time.
export const getStaticProps = async ({ req, res, params: { slug } }) => {
const article = await builder
.get('blog-article', {
req,
res,
// Include references, like our `author` ref
options: { includeRefs: true },
query: {
// Get the specific article by slug
'data.slug': slug,
},
})
.promise();
if (!article) {
return {
notFound: true,
};
}
return { props: { article: article || null, revalidate: 5 } };
};
export async function getStaticPaths() {
// Get a list of all blog article pages in builder
const pages = await builder.getAll('blog-article', {
// We only need the "slug" field
fields: 'data.slug',
options: { noTargeting: true },
});
const paths = pages.map((page) => ({ params: { slug: page.data?.slug } }));
return {
paths,
fallback: 'blocking',
};
}
const BlogArticle = ({ article }: { article: IBlogArticle }) => {
const author = article?.data?.author?.value;
return (
<>
<Header />
<main>
<h1 className="h300 mt-6 max-w-4xl text-center lg:h500">
{article?.data?.title}
</h1>
<div className="mt-10 flex items-center justify-center md:mt-8">
<Avatar
src={author?.data?.photo}
/>
<Image
src={article?.data?.image}
alt={article?.data?.heroImageAltText}
layout="fill"
quality={100}
objectFit="cover"
priority
/>
</div>
<BuilderComponent
name="blog-article"
content={article as unknown}
/>
</main>
<Footer />
</>
);
};
export default BlogArticle;
很甜蜜,不是嗎?我們設法將部落格內容移交給 Builders 平台,同時仍保持對佈局的控制。
對於常見問題頁面,方法與單一部落格頁面相同。
至於第三個(使建構器中的部分可編輯,並反映在主頁上),方法是定義資料模型,在資料擷取方法上查詢它,並透過 props 傳遞資料。這利用了 Next.js 的情監偵戰略,因此對資料的任何更改都會在下一次用戶訪問時傳播。
現在我們已經了解了該方法在 Next.js 應用程式中的工作原理,接下來讓我們深入研究如何使用 Astro 來實現這一點。
將 Builder 連接到 Astro
為了這篇文章,我將建立一個新的 Astro 專案和一個新的 Builder 帳戶。您可以跟隨或直接前往倉庫如果你不耐煩的話🙂。
初始設定
我們首先建立一個新的 Astro 專案:
npm create astro@latest
前往生成器.IO網站並建立一個帳戶。
我們嘗試做的第一件事是在 Astro 中建立一個由 Builder Visual CMS 提供支援的頁面。
從一個新的 Builder 專案開始,您有一個模型,即頁面模型:
若要建立我們的第一個建構器頁面內容,請按照下列步驟操作:
1. 點擊內容
!https://images.tango.us/workflows/f28f33d5-ed12-45ef-a5c2-be9936eb43d1/steps/691ffdba-698d-4dd0-8412-beff5f454514/56017198-6798-699009-560198-696p ng&放大器;作物=焦點&適合=作物&fp-x=0.1017&fp-y=0.1208&fp-z=2.4146&w=1200&mark-w=0.2&mark-pad=0&mark64=aHR0c564=aHR0cLywjwwwx34=aHR0cG hZGUtd2l0aC10YW5nby13YXRlcm1hcmsucG5n& ar=1682%3A1155
2. 建立主頁“頁面”
!https://images.tango.us/workflows/f28f33d5-ed12-45ef-a5c2-be9936eb43d1/steps/a79112a9-a1fb-413c-bb8e-4ac2ff9a4c59/55601413c-bb8e-4ac2ff9a4c59/556014-698b. ng&放大器;作物=焦點&適合=作物&fp-x=0.3986&fp-y=0.1571&fp-z=1.9387&w=1200&mark-w=0.2&mark-pad=0&mark64 =aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21 hZGUtd2l0aC10YW5nby13YXRlcm1hcmsucG5n& ar=1682%3A1155
這是我遇到的第一個麻煩。與 Next.js 不同,Astro 沒有整合文件…所以,我去查看了“整合頁面”頁。在那裡,它們是如何連接不同元框架和前端框架的範例,但正如我所提到的,沒有 Astro。在列出的選項中,唯一可以使用的選項是「Rest API」文件:
我首先遇到的問題是該頁面上顯示的程式碼是快速應用程式整合的範例。由於 Astro 本質上是靜態的,我認為只需從建構器獲取資料即可工作,並且我能夠將其傳遞到我的 Astro 頁面並繼續使用它。
此時我無法使用視覺化 CMS,也無法取得我的內容的資料……🤨
這是先有雞還是先有蛋的問題。我想從視覺化 CMS 獲取數據,但如果連接不正確,我無法透過 Builder 創建某些內容。
應用程式 UI 煞費苦心地告訴我它無法連接到我的網站:
進一步閱讀“整合頁面”文檔,我發現了我缺少的關鍵資訊:
該連結導致“預覽您網站上的內容”部分(Builder HTML API 頁面內)- 此處是使用 HTML API 取得 Builder 預覽的方法。文檔內部有一點循環,只是為了到達它。現在有了這些知識,我們可以返回程式碼並修復 Astro 頁面:
// src/pages/homepage.astro
---
import Layout from '../layouts/Layout.astro';
// the initial code for attempting to get our data from builder
// this does nothing at the moment
// keeping the API key inside our .env file
// for more information: https://docs.astro.build/en/guides/environment-variables/#setting-environment-variables
const apiKey = import.meta.env.BUILDER_API_KEY;
const handleError = (err) => {
console.log(err);
// The requested Builder content could not be found.
if (err.response.status === 404) {
return { data: null };
}
throw err;
};
const encodedUrl = encodeURIComponent('/homepage');
const { data: pageData } = await fetch(
`https://cdn.builder.io/api/v1/html/page?apiKey=${apiKey}&url=${encodedUrl}`
)
.then((res) => res.json())
.catch(handleError);
---
<Layout title='Welcome to Astro Builder'>
<h1>home page</h1>
<builder-component model='page' api-key={apiKey} />
<script async src='https://cdn.builder.io/js/webcomponents'></script>
</Layout>
<style>
// ...
</style>
準備好後,我們就可以開始使用視覺化 CMS 來建立我們的主頁了!
現在我們可以添加一些內容:
現在我們可以點擊「發布」按鈕,看看接下來會發生什麼。
!https://images.tango.us/workflows/aafaff0f-401b-43c4-8bab-84f1221f6f78/steps/ae51e22f-081e-45c8-8af9-9465fdd91acc/d9ace7de-70-46400p-dpng;作物=焦點&適合=作物&fp-x=0.9551&fp-y=0.0229&fp-z=3.0925&w=1200&mark-w=0.2&mark-pad=0&mark64= aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21 hZGUtd2l0aC10YW5nby13YXRlcm1hcmsucG5n& ar=1682%3A1070
我們能夠創建主頁內容模型並實際看到它在我們的應用程式中呈現,但是,為此我們在 Astro 頁面中添加了一個 Web 元件。這意味著,我們需要 JavaScript 才能渲染頁面。這並不理想,因為 Astro 首先是靜態的,我們希望能夠靜態地取得該內容。
此時,如果我們使用以下命令建立 Astro 專案:
npm run build
然後我們可以使用以下命令運行生產建置:
npm run preview
然後我們可以前往localhost:3000/homepage
並彈出我們的開發工具,我們可以看到兩件事:
-
查看網絡,有一個針對 Builder 的 CDN 的 http 請求以獲取其 Web 組件腳本:
-
如果我們停用 JS,我們將無法從 Builder 取得內容:
連接 HTML API 和靜態渲染
現在讓我們看看如何從建構器取得預先渲染的內容,這樣我們就可以擁有一個完全靜態的頁面。
如果我們回到 homepage.astro
上的 Astro 程式碼,我們現在可以在終端輸出中看到我們確實從建構器取得了資料:
// src/pages/homepage.astro
---
...
const apiKey = import.meta.env.BUILDER_API_KEY;
...
const encodedUrl = encodeURIComponent('/homepage');
const { data: pageData } = await fetch(
`https://cdn.builder.io/api/v1/html/page?apiKey=${apiKey}&url=${encodedUrl}`
)
.then((res) => res.json())
.catch(handleError);
---
...
記錄 pageData
回應,我們可以看到我們得到了一個具有 html
屬性的物件:
{
"createdBy": "9EsPg4I95mcHGVqloFHcEqq2dop2",
"createdDate": 1667036699251,
"data": {
"themeId": false,
"title": "Homepage",
"url": "/homepage",
"html": "<builder-component rev=\"w7gjtbab0p8\" api-key=\"b5760280b2464ac990288c03c4b8b1bc\" name=\"page\" entry=\"e18ae8ea06cd41a6a69f4d178de4dc1c\">\n\n<!-- ***** Generated by Builder.io on Sat, 29 Oct 2022 15:39:21 GMT ***** -->\n\n<style type=\"text/css\" class=\"builder-styles builder-api-styles\">/*start:h47494*/.css-h47494{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;}/*end:h47494*/ /*start:hgfgng*/.css-hgfgng.builder-block{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;position:relative;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;margin-top:20px;line-height:normal;height:auto;}/*end:hgfgng*/ /*start:1qggkls*/.css-1qggkls{outline:none;}.css-1qggkls p:first-of-type,.css-1qggkls .builder-paragraph:first-of-type{margin:0;}.css-1qggkls > p,.css-1qggkls .builder-paragraph{color:inherit;line-height:inherit;-webkit-letter-spacing:inherit;-moz-letter-spacing:inherit;-ms-letter-spacing:inherit;letter-spacing:inherit;font-weight:inherit;font-size:inherit;text-align:inherit;font-family:inherit;}/*end:1qggkls*/ /*start:q80dxn*/.css-q80dxn.builder-block{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;position:relative;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;margin-top:20px;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;opacity:0;-webkit-transform:translate3d(0,20px,0);-ms-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0);}/*end:q80dxn*/ /*start:1840m1q*/.css-1840m1q{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}@media (max-width:999px){.css-1840m1q{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;}}/*end:1840m1q*/ /*start:2keume*/.css-2keume{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;line-height:normal;width:calc(50% - 10px);margin-left:0;}.css-2keume > .builder-blocks{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;}@media (max-width:999px){.css-2keume{width:100%;margin-left:0;}}/*end:2keume*/ /*start:dichlt*/.css-dichlt.builder-block{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;position:relative;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;margin-top:auto;-webkit-appearance:none;-moz-appearance:none;appearance:none;padding-top:15px;padding-bottom:15px;padding-left:25px;padding-right:25px;background-color:black;color:white;border-radius:4px;text-align:center;cursor:pointer;margin-bottom:auto;}/*end:dichlt*/ /*start:bst442*/.css-bst442{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;line-height:normal;width:calc(50% - 10px);margin-left:20px;}.css-bst442 > .builder-blocks{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;}@media (max-width:999px){.css-bst442{width:100%;margin-left:0;}}/*end:bst442*/ /*start:1xp67e9*/.css-1xp67e9.builder-block{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;position:relative;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;margin-top:20px;width:100%;min-height:20px;min-width:20px;overflow:hidden;margin-left:auto;margin-right:auto;max-width:200px;height:auto;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;}/*end:1xp67e9*/ /*start:12153wi*/.css-12153wi{opacity:1;-webkit-transition:opacity 0.2s ease-in-out;transition:opacity 0.2s ease-in-out;object-fit:cover;object-position:center;position:absolute;height:100%;width:100%;left:0;top:0;}/*end:12153wi*/ /*start:1aa8xmo*/.css-1aa8xmo{width:100%;padding-top:149.9%;pointer-events:none;font-size:0;}/*end:1aa8xmo*/ /*start:qguwqq*/.css-qguwqq.builder-block{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;position:relative;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;margin-top:20px;line-height:normal;height:auto;text-align:center;}/*end:qguwqq*/ /*start:1mvsfya*/.css-1mvsfya.builder-block{height:0;width:0;display:inline-block;opacity:0;overflow:hidden;pointer-events:none;}/*end:1mvsfya*/</style><div class=\"builder-component builder-component-e18ae8ea06cd41a6a69f4d178de4dc1c\" data-name=\"page\" data-source=\"Rendered by Builder.io\"><div class=\"builder-content\" builder-content-id=\"e18ae8ea06cd41a6a69f4d178de4dc1c\" builder-model=\"page\"><div data-builder-component=\"page\" data-builder-content-id=\"e18ae8ea06cd41a6a69f4d178de4dc1c\" data-builder-variation-id=\"e18ae8ea06cd41a6a69f4d178de4dc1c\"><style data-emotion-css=\"h47494\"></style><div class=\"builder-blocks css-h47494\" builder-type=\"blocks\"><style data-emotion-css=\"hgfgng\"></style><div class=\"builder-block builder-15820be824444228ace6b6fd8a8f83be builder-has-component css-hgfgng\" builder-id=\"builder-15820be824444228ace6b6fd8a8f83be\"><style data-emotion-css=\"1qggkls\"></style><span class=\"builder-text css-1qggkls\"><h3>I'm a paragraph from Builder.io!</h3></span></div><style data-emotion-css=\"q80dxn\"></style><div class=\"builder-block builder-09d46f33071a4caaa7417383ac0ca6e9 builder-has-component css-q80dxn\" builder-id=\"builder-09d46f33071a4caaa7417383ac0ca6e9\"><style data-emotion-css=\"1840m1q\"></style><div class=\"builder-columns css-1840m1q\"><style data-emotion-css=\"2keume\"></style><div class=\"builder-column css-2keume\"><div class=\"builder-blocks builder-blocks-child css-h47494\" builder-type=\"blocks\"><style data-emotion-css=\"dichlt\"></style><span class=\"builder-block builder-314766e257534dc1be477c49a79e1674 builder-has-component css-dichlt\" builder-id=\"builder-314766e257534dc1be477c49a79e1674\">I'm a button from Builder</span></div></div><style data-emotion-css=\"bst442\"></style><div class=\"builder-column css-bst442\"><div class=\"builder-blocks builder-blocks-child css-h47494\" builder-type=\"blocks\"><style data-emotion-css=\"1xp67e9\"></style><div class=\"builder-block builder-19a69fd2137a41339610cedad5dadf91 css-1xp67e9\" builder-id=\"builder-19a69fd2137a41339610cedad5dadf91\"><picture><source srcSet=\"https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?format=webp&width=100 100w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?format=webp&width=200 200w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?format=webp&width=400 400w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?format=webp&width=800 800w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?format=webp&width=1200 1200w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?format=webp&width=1600 1600w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?format=webp&width=2000 2000w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4\" type=\"image/webp\"/><style data-emotion-css=\"12153wi\"></style><img role=\"presentation\" loading=\"lazy\" class=\"builder-image css-12153wi\" src=\"https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4\" srcSet=\"https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?width=100 100w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?width=200 200w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?width=400 400w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?width=800 800w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?width=1200 1200w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?width=1600 1600w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4?width=2000 2000w, https://cdn.builder.io/api/v1/image/assets%2Fb5760280b2464ac990288c03c4b8b1bc%2F7d8ad6b7a9b64e75ac112efc0ad685f4\" sizes=\"100vw\"/></picture><style data-emotion-css=\"1aa8xmo\"></style><div class=\"builder-image-sizer css-1aa8xmo\"> </div></div><style data-emotion-css=\"qguwqq\"></style><div class=\"builder-block builder-94d4c3fc1db44fcaa5886916db94821d builder-has-component css-qguwqq\" builder-id=\"builder-94d4c3fc1db44fcaa5886916db94821d\"><span class=\"builder-text css-1qggkls\"><p>The image above me is from Builder!</p><p><br></p></span></div></div></div></div></div><style data-emotion-css=\"1mvsfya\"></style><img src=\"https://cdn.builder.io/api/v1/pixel?apiKey=b5760280b2464ac990288c03c4b8b1bc\" role=\"presentation\" width=\"0\" height=\"0\" class=\"builder-block builder-pixel-uv14r32jplb css-1mvsfya\" builder-id=\"builder-pixel-uv14r32jplb\"/></div></div></div></div></builder-component>\n<script async src=\"https://cdn.builder.io/js/webcomponents\"></script>",
"animations": [
{
"trigger": "scrollInView",
"animation": "fadeInUp",
"steps": [
{
"id": "468d51e36f704cd29941c48be82cd36d",
"isStartState": false,
"styles": {
"opacity": "0",
"transform": "translate3d(0, 20px, 0)"
},
"delay": 0
},
{
"id": "070b1bb2f32048c5ba8fab3ba54f6b80",
"isStartState": false,
"styles": {
"opacity": "1",
"transform": "none"
},
"delay": 0
}
],
"delay": 0,
"duration": 0.5,
"easing": "cubic-bezier(.37,.01,0,.98)",
"repeat": false,
"thresholdPercent": 0,
"elementId": "builder-09d46f33071a4caaa7417383ac0ca6e9",
"id": "builder-09d46f33071a4caaa7417383ac0ca6e9"
},
{
"trigger": "scrollInView",
"animation": "fadeInUp",
"steps": [
{
"id": "468d51e36f704cd29941c48be82cd36d",
"isStartState": false,
"styles": {
"opacity": "0",
"transform": "translate3d(0, 20px, 0)"
},
"delay": 0
},
{
"id": "070b1bb2f32048c5ba8fab3ba54f6b80",
"isStartState": false,
"styles": {
"opacity": "1",
"transform": "none"
},
"delay": 0
}
],
"delay": 0,
"duration": 0.5,
"easing": "cubic-bezier(.37,.01,0,.98)",
"repeat": false,
"thresholdPercent": 0,
"elementId": "builder-09d46f33071a4caaa7417383ac0ca6e9",
"id": "builder-09d46f33071a4caaa7417383ac0ca6e9"
}
]
},
"id": "e18ae8ea06cd41a6a69f4d178de4dc1c",
"lastUpdatedBy": "9EsPg4I95mcHGVqloFHcEqq2dop2",
"meta": {
"hasLinks": false,
"kind": "page",
"lastPreviewUrl": "http://localhost:3000/homepage?builder.space=b5760280b2464ac990288c03c4b8b1bc&builder.cachebust=true&builder.preview=page&builder.noCache=true&__builder_editing__=true&builder.overrides.page=e18ae8ea06cd41a6a69f4d178de4dc1c&builder.overrides.e18ae8ea06cd41a6a69f4d178de4dc1c=e18ae8ea06cd41a6a69f4d178de4dc1c&builder.overrides.page:/homepage=e18ae8ea06cd41a6a69f4d178de4dc1c"
},
"modelId": "7f9f4de4804a43cc9e2b1c763c5febe5",
"name": "Homepage",
"published": "published",
"query": [
{
"@type": "@builder.io/core:Query",
"operator": "is",
"property": "urlPath",
"value": "/homepage"
}
],
"testRatio": 1,
"lastUpdated": 1667057439580,
"firstPublished": 1667057439579,
"rev": "w7gjtbab0p8"
}
讓我們看看 Astro 如何透過將回應傳遞到我們的頁面來處理這些資料:
<Layout title="Welcome to Astro Builder">
<h1>home page</h1>
{pageData.html}
</Layout>
然而,僅僅將該變數傳遞到我們的 Astro 元件中會產生不太好的輸出:
它基本上將所有內容渲染為字串。讓我們看看是否可以解決這個問題…
對我們來說幸運的是,Astro 有一種方法來處理原始字串 HTMLset:html
模板指令。讓我們稍微改變一下程式碼來使用它:
<Layout title="Welcome to Astro Builder">
<h1>home page</h1>
<main set:html={pageData.html}></main>
</Layout>
現在,當我們渲染頁面時,結果如下:
但是,在查看應用程式建置的輸出後(再次執行 npm run build
然後 npm run preview
),我們可以看到它仍然是一個底層網路需要JS 和客戶端網路請求才能工作的元件:
當然,如果我們停用 JS,我們最終會得到比以前更好的輸出,但大部分都是空白頁面:
不過我們確實從 Builder 那裡得到了那段話…
所以,我們能做些什麼?
- 嘗試並使用
content-api
- 嘗試並使用
qwik-api
- 將
BuilderComponent
與 SDK 中的元件一起使用。
讓我們來看看它們。
內容API
如果我們繼續使用我們的憑證查詢內容 API,僅從回應的外觀(下面的部分輸出)我們就可以看到,為了讓我們使用 data
屬性,我們必須解析回應並創建某種渲染函數來幫助我們。
{
"results": [
{
"createdBy": "9EsPg4I95mcHGVqloFHcEqq2dop2",
"createdDate": 1667036699251,
"data": {
"inputs": [
],
"themeId": false,
"title": "Homepage",
"blocks": [
{
"@type": "@builder.io/sdk:Element",
"@version": 2,
"id": "builder-15820be824444228ace6b6fd8a8f83be",
"component": {
"name": "Text",
"options": {
"text": "<h3>I'm a paragraph from Builder.io!</h3>"
}
},
"responsiveStyles": {
"large": {
"display": "flex",
"flexDirection": "column",
"position": "relative",
"flexShrink": "0",
"boxSizing": "border-box",
"marginTop": "20px",
"lineHeight": "normal",
"height": "auto"
}
}
},
// ...
]
}
]
}
快客API
Qwik 是 Builder 的開源專案之一。它是一種由可恢復性驅動的新型前端框架。 (更深入地了解 Qwik讀這個)。我們可以使用 cURL 請求來查詢 Qwik API,如下所示:
curl --request GET \
--url 'https://cdn.builder.io/api/v1/qwik/page?url=http%3A%2F%2Flocalhost%3A3000%2Fhomepage&apiKey=b5760280b2464ac990288c03c4b8b1bc&page=%2Fhomepage&limit=1'
回應也有一個類似的回應,其中包含 data
屬性和嵌套在其中的 blocks
屬性。不過,還有一個 html
屬性。
讓我們嘗試在 Astro 程式碼中使用它:
// src/pages/homepage.astro
---
import Layout from '../layouts/Layout.astro';
const apiKey = import.meta.env.BUILDER_API_KEY;
const handleError = (err: any) => {
console.log(err);
// The requested Builder content could not be found.
if (err.response.status === 404) {
return { data: null };
}
throw err;
};
const encodedUrl = encodeURIComponent('/homepage');
const qwikPageData = await fetch(
`https://cdn.builder.io/api/v1/qwik/page?apiKey=${apiKey}&url=${encodedUrl}`
)
.then((res) => res.json())
.catch(handleError);
---
<Layout title='Welcome to Astro Builder'>
<h1>home page</h1>
<main set:html={qwikPageData.html}></main>
</Layout>
現在,我們將再次運行npm run build
,然後運行npm run preview
,檢查構建輸出的樣子。
你瞧!我們已經成功地將所有內容靜態化:
同樣,我們可以透過停用 JavaScript 並重新載入頁面來驗證這一點。相信我,它有效 🙂
SDK建構器元件
為此,我們需要決定我們想要將元件放入哪個框架中,我們將使用@builder.io/react
。
現在,為了使其在 Astro 中發揮作用,我們需要採取一些步驟:
-
將 React 加到 astro – 執行
npx astro add react
並依照提示操作。 -
定義 Builder/React 元件:
// src/components/ReactBuilder.tsx
import { BuilderComponent } from ‘@builder.io/react’;
export const BuilderReact = ({ builderJson }: { builderJson: any }) => {
return (
<>
</>
);
}; -
連接到我們的數據:
// src/pages/homepage.astro
import { BuilderReact } from ‘../components/ReactBuilder’;
import { builder } from ‘@builder.io/react’;const apiKey = import.meta.env.BUILDER_API_KEY;
builder.init(apiKey);
const builderJson = await builder.get(‘page’, { url: ‘/homepage’ }).promise();
home page
;
瞧!
我們的頁面壞了…🤦🏽♂️
我們有了內容,但沒有 CSS。這是由於 Astro 的靜態特性。該元件從伺服器載入數據,但無法自行補充數據。使用 Astro 的 client
指令標記 React 元件並不能解決問題(相信我,我已經嘗試過了😉),因為我們需要從客戶端的 Builder 取得資料。
讓我們嘗試透過以下方式將所有資料擷取移至元件本身來解決此問題Builder 文件網站中的 React 範例:
// src/components/ReactBuilder.tsx
import { useEffect, useState } from 'react';
import { BuilderComponent, builder, useIsPreviewing } from '@builder.io/react';
// NOTE: now we need to expose the API key to the client.
// See: https://vitejs.dev/guide/env-and-mode.html#env-files
const apiKey = import.meta.env.VITE_BUILDER_API_KEY;
// For React Builder
builder.init(apiKey);
// set whether you're using the Visual Editor,
// whether there are changes,
// and render the content if found
export function BuilderReact() {
const isPreviewingInBuilder = useIsPreviewing();
const [notFound, setNotFound] = useState(false);
const [content, setContent] = useState(null);
// get the page content from Builder
useEffect(() => {
async function fetchContent() {
const content = await builder
.get('page', {
url: window.location.pathname,
})
.promise();
setContent(content);
setNotFound(!content);
}
fetchContent();
}, [window.location.pathname]);
return (
<>
<head>
<title>{content?.data.title}</title>
</head>
{/* Render the Builder page */}
<BuilderComponent model="page" content={content} />
</>
);
}
這破壞了我們的應用程式……🙄
再次查看終端機中的錯誤訊息,我們得到了所有全端框架中最煩人的訊息:
window is not defined
嘗試不同的指令,例如 cilent:visable
、client:idle
和 client:media
對於該錯誤沒有太大作用。然而,當我嘗試 client:only='react'
時,終端訊息消失了,有一絲希望,但無濟於事 😔 – 我們看不到內容……
此時我已經放棄了這種方法,因為這可能會變成另一篇文章😅。
結論
在我看來,Builder 的 Visual CMS 改變了開發人員的遊戲規則。它可以為您節省時間、金錢,並解決許多令人頭痛的問題。由於 Astro 使用起來非常令人愉悅,因此有趣的是看到另一個快樂和富有成效的東西可以與這個堆疊結合在一起。我知道,只要有人說“博客”或“嘿,我們可以更改主頁英雄部分的標題嗎?”,我肯定會使用這個工具。