Thiết lập blog solo miễn phí tuyệt đỉnh với Ghost và Gatsby

theanh

Administrator
Nhân viên
Ngày nay, có vẻ như có vô số công cụ và nền tảng để tạo blog của riêng bạn. Tuy nhiên, rất nhiều tùy chọn ngoài kia hướng đến người dùng không chuyên và trừu tượng hóa tất cả các tùy chọn để tùy chỉnh và thực sự biến thứ gì đó thành của riêng bạn.

Nếu bạn là người hiểu biết về phát triển front-end, bạn có thể thấy khó chịu khi tìm ra giải pháp cung cấp cho bạn quyền kiểm soát mong muốn, trong khi lại loại bỏ quyền quản trị khỏi việc quản lý nội dung blog của bạn.

Hãy đến với Hệ thống quản lý nội dung không đầu (CMS). Với CMS không đầu, bạn có thể có tất cả các công cụ để tạo và sắp xếp nội dung của mình, đồng thời vẫn duy trì 100% quyền kiểm soát cách thức phân phối nội dung đến người đọc. Nói cách khác, bạn có được toàn bộ cấu trúc phụ trợ của CMS trong khi không bị giới hạn bởi các chủ đề và mẫu front-end cứng nhắc của nó.

Khi nói đến hệ thống CMS không đầu, tôi rất thích Ghost. Ghost là mã nguồn mở và dễ sử dụng, với nhiều API tuyệt vời giúp nó linh hoạt khi sử dụng với các trình xây dựng trang web tĩnh như Gatsby.

Trong bài viết này, tôi sẽ chỉ cho bạn cách sử dụng Ghost và Gatsby cùng nhau để có được thiết lập blog cá nhân tối ưu cho phép bạn kiểm soát hoàn toàn việc phân phối giao diện người dùng, nhưng để lại mọi công việc quản lý nội dung nhàm chán cho Ghost.

Ồ, và việc thiết lập và chạy nó hoàn toàn miễn phí. Đó là vì chúng tôi sẽ chạy phiên bản Ghost của mình cục bộ và sau đó triển khai lên Netlify, tận dụng lợi thế của gói miễn phí hào phóng của họ.

Chúng ta hãy cùng tìm hiểu!

Thiết lập Ghost và Gatsby​

Tôi đã viết một bài đăng khởi đầu về điều này trước đây, bài đăng này đề cập đến những điều rất cơ bản, vì vậy tôi sẽ không đi sâu vào chúng ở đây. Thay vào đó, tôi sẽ tập trung vào các vấn đề và lỗi nâng cao hơn phát sinh khi chạy blog không có giao diện.

Nhưng tóm lại, đây là những gì chúng ta cần làm để thiết lập và chạy cơ bản mà chúng ta có thể làm việc:
  • Cài đặt phiên bản cục bộ của Gatsby Starter Blog
  • Cài đặt Ghost cục bộ
  • Thay đổi dữ liệu nguồn từ Markdown thành Ghost (hoán đổi hệ thống gatsby-source-file thành gatsby-source-ghost)
  • Sửa đổi các truy vấn GraphQL trong gatsby-node, các mẫu và các trang của bạn để phù hợp với lược đồ gatsby-source-ghost
Để biết thêm chi tiết về bất kỳ bước nào trong số các bước này, bạn có thể xem bài viết trước của tôi.

Hoặc bạn có thể bắt đầu từ mã trong kho lưu trữ Github này.

Xử lý hình ảnh​

Sau khi đã nắm được những kiến thức cơ bản, vấn đề đầu tiên chúng ta gặp phải với một blog không có giao diện được xây dựng cục bộ là phải làm gì với hình ảnh.

Theo mặc định, Ghost cung cấp hình ảnh từ máy chủ của riêng mình. Vì vậy, khi bạn chuyển sang trang web tĩnh không có giao diện, bạn sẽ gặp phải tình huống nội dung của mình được xây dựng và phục vụ từ nhà cung cấp biên như Netlify, nhưng hình ảnh của bạn vẫn được phục vụ bởi máy chủ Ghost của bạn.

Điều này không lý tưởng về mặt hiệu suất và khiến việc xây dựng và triển khai trang web cục bộ trở nên bất khả thi (có nghĩa là bạn sẽ phải trả phí hàng tháng cho một droplet Digital Ocean, phiên bản AWS EC2 hoặc một số máy chủ khác để lưu trữ phiên bản Ghost của mình).

Nhưng chúng ta có thể giải quyết vấn đề đó nếu tìm được giải pháp khác để lưu trữ hình ảnh &mdash và may mắn thay, Ghost có bộ chuyển đổi lưu trữ cho phép bạn lưu trữ hình ảnh trên đám mây.

Đối với mục đích của mình, chúng ta sẽ sử dụng bộ chuyển đổi AWS S3, cho phép chúng ta lưu trữ hình ảnh của mình trên AWS S3 cùng với Cloudfront để mang lại cho chúng ta hiệu suất tương tự như phần còn lại của nội dung.

Có hai tùy chọn nguồn mở khả dụng: ghost-storage-adapter-s3ghost-s3-compat. Tôi sử dụng ghost-storage-adapter-s3 vì tôi thấy tài liệu dễ theo dõi hơn và nó được cập nhật gần đây hơn.

Mặc dù vậy, nếu tôi làm theo tài liệu chính xác, tôi đã gặp một số lỗi AWS, vì vậy đây là quy trình mà tôi đã làm theo và hiệu quả với tôi:
  • Tạo một S3 Bucket mới trong AWS và chọn Tắt lưu trữ tĩnh
  • Tiếp theo, tạo một Cloudfront Distribution mới và chọn S3 Bucket làm Nguồn
  • Khi cấu hình Cloudfront Distribution, trong mục Truy cập S3 Bucket:Chọn “Có, sử dụng OAI (bucket chỉ có thể hạn chế quyền truy cập vào Cloudfront)”
  • Tạo một OAI mới
  • Cuối cùng, chọn “Có, cập nhật chính sách bucket”

Điều này tạo ra một AWS S3 Bucket mà chỉ có thể được truy cập thông qua Cloudfront Distribution mà bạn đã tạo.

Sau đó, bạn chỉ cần tạo một Người dùng IAM cho Ghost để cho phép nó ghi hình ảnh mới vào S3 Bucket mới của bạn. Để thực hiện việc này, hãy tạo Người dùng IAM theo chương trình mới và đính kèm chính sách này vào đó:
Mã:
{ "Phiên bản": "17-10-2012", "Câu lệnh": [ { "Sid": "VisualEditor0", "Hiệu ứng": "Cho phép", "Hành động": "s3:ListBucket", "Tài nguyên": "arn:aws:s3:::TÊN-HỘP-S3-SOUR" }, { "Sid": "VisualEditor1", "Hiệu ứng": "Cho phép", "Hành động": [ "s3:PutObject", "s3:GetObject", "s3:PutObjectVersionAcl", "s3:DeleteObject", "s3:PutObjectAcl" ], "Tài nguyên": "arn:aws:s3:::TÊN-HỘP-S3-SOUR/*" } ]}
Với điều đó, thiết lập AWS của chúng ta đã hoàn tất, chúng ta chỉ cần yêu cầu Ghost đọc và ghi hình ảnh của chúng ta ở đó thay vì ghi vào máy chủ cục bộ của nó.

Để thực hiện điều đó, chúng ta cần đến thư mục nơi cài đặt phiên bản Ghost của mình và mở tệp: ghost.development.json hoặc ghost.production.json. (tùy thuộc vào môi trường bạn đang chạy)

Sau đó, chúng ta chỉ cần thêm nội dung sau:
Mã:
{ "storage": { "active": "s3", "s3": { "accessKeyId": "[key]", "secretAccessKey": "[secret]", "region": "[region]", "bucket": "[bucket]", "assetHost": "https://[subdomain].example.com", // cloudfront "forcePathStyle": true, "acl": "private" }}
Các giá trị cho accessKeyIdsecretAccessKey có thể được tìm thấy từ thiết lập IAM của bạn, trong khi vùng và thùng tham chiếu đến vùng và tên thùng của thùng S3 của bạn. Cuối cùng, assetHost là URL của bản phân phối Cloudfront của bạn.

Bây giờ, nếu bạn khởi động lại phiên bản Ghost của mình, bạn sẽ thấy rằng bất kỳ hình ảnh mới nào bạn lưu đều nằm trong thùng S3 của bạn và Ghost biết cách liên kết đến chúng ở đó. (Lưu ý: Ghost sẽ không thực hiện cập nhật theo cách hồi tố, vì vậy hãy đảm bảo thực hiện việc này ngay sau khi cài đặt Ghost mới để bạn không phải tải lại hình ảnh sau này)

Xử lý Liên kết Nội bộ​

Sau khi đã giải quyết xong Hình ảnh, điều khó khăn tiếp theo mà chúng ta cần nghĩ đến là liên kết nội bộ. Khi bạn viết nội dung trong Ghost và chèn liên kết vào Bài đăng và Trang, Ghost sẽ tự động thêm URL của trang web vào tất cả các liên kết nội bộ.

Ví dụ, nếu bạn đặt một liên kết trong bài đăng trên blog của mình dẫn đến /my-post/, Ghost sẽ tạo một liên kết dẫn đến https://mysite.com/my-post/.

Thông thường, đây không phải là vấn đề lớn, nhưng đối với các blog Headless thì điều này gây ra sự cố. Điều này là do phiên bản Ghost của bạn sẽ được lưu trữ ở đâu đó tách biệt với giao diện người dùng và trong trường hợp của chúng tôi, nó thậm chí sẽ không thể truy cập trực tuyến vì chúng tôi sẽ xây dựng cục bộ.

Điều này có nghĩa là chúng tôi sẽ cần phải xem qua từng bài đăng trên blog và trang để sửa bất kỳ liên kết nội bộ nào. May mắn thay, điều này không khó như bạn nghĩ.

Đầu tiên, chúng ta sẽ thêm tập lệnh phân tích cú pháp HTML này vào một tệp mới có tên là replaceLinks.js và đặt nó vào một thư mục utils mới tại src/utils:
Mã:
const url = require(`url`);const cheerio = require('cheerio');const replaceLinks = async (htmlInput, siteUrlString) => { const siteUrl = url.parse(siteUrlString); const $ = cheerio.load(htmlInput); const links = $('a'); links.attr('href', function(i, href){ if (href) { const hrefUrl = url.parse(href); if (hrefUrl.protocol === siteUrl.protocol && hrefUrl.host === siteUrl.host) { return hrefUrl.path } return href; } }); return $.html();}module.exports = replaceLinks;
Sau đó, chúng ta sẽ thêm nội dung sau vào tệp gatsby-node.js của mình:
Mã:
exports.onCreateNode = async ({ hành động, nút, getNodesByType }) => { nếu (node.internal.owner !== `gatsby-source-ghost`) { trả về } nếu (node.internal.type === 'GhostPage' || node.internal.type === 'GhostPost') { const cài đặt = getNodesByType(`GhostSettings`); actions.createNodeField({ name: 'html', value: replaceLinks(node.html, settings[0].url), node }) }}
Bạn sẽ thấy chúng tôi đang thêm hai gói mới vào replaceLinks.js, vì vậy hãy bắt đầu bằng cách cài đặt chúng bằng NPM:
Mã:
npm install --save url cheerio
Trong tệp gatsby-node.js của chúng tôi, chúng tôi đang móc vào onCreateNode của Gatsby và cụ thể là vào bất kỳ nút nào được tạo từ dữ liệu đến từ gatsby-source-ghost (trái ngược với siêu dữ liệu đến từ tệp cấu hình mà chúng tôi hiện không quan tâm).

Sau đó, chúng tôi đang kiểm tra loại nút để lọc ra bất kỳ nút nào không phải Ghost Pages hoặc Posts (vì đây là những trang duy nhất có liên kết bên trong nội dung của chúng).

Tiếp theo, chúng ta sẽ lấy URL của trang Ghost từ cài đặt Ghost và truyền URL đó cho hàm removeLinks của chúng ta cùng với nội dung HTML từ Page/Post.

Trong replaceLinks, chúng ta sử dụng cheerio để phân tích cú pháp HTML. Sau đó, chúng ta có thể chọn tất cả các liên kết trong nội dung HTML này và ánh xạ qua các thuộc tính href của chúng. Sau đó, chúng ta có thể kiểm tra xem thuộc tính href có khớp với URL của Ghost Site hay không — nếu khớp, chúng ta sẽ thay thế thuộc tính href chỉ bằng đường dẫn URL, tức là liên kết nội bộ mà chúng ta đang tìm kiếm (ví dụ: /my-post/).

Cuối cùng, chúng ta sẽ cung cấp nội dung HTML mới này thông qua GraphQL bằng cách sử dụng createNodeField của Gatsby (Lưu ý: chúng ta phải thực hiện theo cách này vì Gatsby không cho phép bạn ghi đè các trường ở giai đoạn này trong quá trình xây dựng).

Bây giờ, nội dung HTML mới của chúng ta sẽ có trong mẫu blog-post.js và chúng ta có thể truy cập bằng cách thay đổi truy vấn GraphQL thành:
Mã:
ghostPost(slug: { eq: $slug }) { id title slug excerpt published_at_pretty: published_at(formatString: "DD MMMM, YYYY") html meta_title fields { html }}
Và với điều đó, chúng ta chỉ cần chỉnh sửa phần này trong mẫu:
Mã:
Để là:
Mã:
Điều này giúp tất cả các liên kết nội bộ của chúng ta có thể truy cập được, nhưng chúng ta vẫn còn một vấn đề nữa. Tất cả các liên kết này đều là thẻ neo trong khi với Gatsby, chúng ta nên sử dụng Liên kết Gatsby cho các liên kết nội bộ (để tránh làm mới trang và mang lại trải nghiệm liền mạch hơn).

Rất may là có một plugin Gatsby giúp giải quyết vấn đề này một cách dễ dàng. Nó được gọi là gatsby-plugin-catch-links và nó tìm kiếm bất kỳ liên kết nội bộ nào và tự động thay thế các thẻ neo bằng Gatsby.

Tất cả những gì chúng ta cần làm là cài đặt nó bằng NPM:
Mã:
npm install --save gatsby-plugin-catch-links
Và thêm gatsby-plugin-catch-links vào mảng plugin của chúng ta trong tệp gatsby-config.

Thêm mẫu và kiểu​

Bây giờ, về mặt kỹ thuật, mọi thứ lớn đã hoạt động, nhưng chúng ta đang bỏ lỡ một số nội dung từ phiên bản Ghost của mình.

Gatsby Starter Blog chỉ có trang Index và mẫu cho Blog Posts, trong khi Ghost mặc định có Posts, Pages, cũng như các trang cho Tags và Authors. Vì vậy, chúng ta cần tạo mẫu cho từng mục này.

Để làm được điều này, chúng ta có thể tận dụng Gatsby starter do nhóm Ghost tạo ra.

Là điểm khởi đầu cho dự án này, chúng ta có thể chỉ cần sao chép và dán nhiều tệp trực tiếp vào dự án của mình. Sau đây là những gì chúng ta sẽ thực hiện:
  • Toàn bộ thư mục src/components/common/meta — chúng ta sẽ sao chép thư mục này vào thư mục src/components của chúng ta (vì vậy bây giờ chúng ta sẽ có một thư mục src/components/meta)
  • Các tệp thành phần Pagination.jsPostCard.js — chúng ta sẽ sao chép các tệp này vào Thư mục src/components
  • Chúng tôi sẽ tạo một thư mục src/utils và thêm hai tệp từ thư mục src/utils của chúng: fragments.jssiteConfig.js
  • Và các mẫu sau từ thư mục src/templates của chúng: tag.js, page.js, author.jspost.js
Các tệp meta đang thêm đánh dấu dữ liệu có cấu trúc JSON vào các mẫu của chúng tôi. Đây là một lợi ích tuyệt vời mà Ghost cung cấp theo mặc định trên nền tảng của họ và họ đã chuyển nó vào Gatsby như một phần của mẫu khởi động của họ.

Sau đó, chúng tôi lấy các thành phần Phân trangPostCard.js mà chúng tôi có thể thả ngay vào dự án của mình. Và với các thành phần đó, chúng ta có thể lấy các tệp mẫu và thả chúng vào dự án của mình và chúng sẽ hoạt động.

Tệp fragments.js giúp các truy vấn GraphQL của chúng ta gọn gàng hơn rất nhiều cho từng trang và mẫu — giờ đây chúng ta chỉ có một nguồn trung tâm cho tất cả các truy vấn GraphQL của mình. Và tệp siteConfig.js có một vài tùy chọn cấu hình Ghost dễ dàng nhất để đưa vào một tệp riêng.

Bây giờ chúng ta chỉ cần cài đặt một vài gói npm và cập nhật tệp gatsby-node của mình để sử dụng các mẫu mới.

Các gói mà chúng ta cần cài đặt là gatsby-awesome-pagination, @tryghost/helpers@tryghost/helpers-gatsby.

Vì vậy, chúng ta sẽ thực hiện:
Mã:
npm install --save gatsby-awesome-pagination @tryghost/helpers @tryghost/helpers-gatsby
Sau đó, chúng ta cần thực hiện một số cập nhật cho Tệp gatsby-node.

Đầu tiên, chúng ta sẽ thêm các mục nhập mới sau vào đầu tệp của mình:
Mã:
const { paginate } = require(`gatsby-awesome-pagination`);const { postsPerPage } = require(`./src/utils/siteConfig`);
Tiếp theo, trong exports.createPages, chúng ta sẽ cập nhật truy vấn GraphQL thành:
Mã:
{ allGhostPost(sort: { order: ASC, fields: published_at }) { edges { node { slug } } } allGhostTag(sort: { order: ASC, fields: name }) { edges { node { slug url postCount } } } allGhostAuthor(sort: { order: ASC, fields: name }) { edges { node { slug url postCount } } } allGhostPage(sort: { order: ASC, fields: published_at }) { edges { node { slug url } } }}
Điều này sẽ kéo tất cả dữ liệu GraphQL mà chúng ta cần cho Gatsby để xây dựng các trang dựa trên các mẫu mới của chúng tôi.

Để thực hiện điều đó, chúng tôi sẽ trích xuất tất cả các truy vấn đó và gán chúng cho các biến:
Mã:
// Trích xuất kết quả truy vấn const tags = result.data.allGhostTag.edges const authors = result.data.allGhostAuthor.edges const pages = result.data.allGhostPage.edges const posts = result.data.allGhostPost.edges
Sau đó, chúng tôi sẽ tải tất cả các mẫu của mình:
Mã:
// Tải các mẫu const tagsTemplate = path.resolve(`./src/templates/tag.js`) const authorTemplate = path.resolve(`./src/templates/author.js`) const pageTemplate = path.resolve(`./src/templates/page.js`) const postTemplate = path.resolve(`./src/templates/post.js`)
Lưu ý ở đây là chúng ta đang thay thế mẫu blog-post.js cũ bằng post.js, do đó chúng ta có thể tiếp tục và xóa blog-post.js khỏi thư mục mẫu của mình.

Cuối cùng, chúng ta sẽ thêm mã này để xây dựng các trang từ mẫu và dữ liệu GraphQL của mình:
Mã:
// Tạo trang thẻtags.forEach(({ node }) => { const totalPosts = node.postCount !== null ? node.postCount : 0 // Phần này ở đây định nghĩa rằng các trang thẻ của chúng ta sẽ sử dụng // liên kết cố định `/tag/:slug/`. const url = `/tag/${node.slug}` const items = Array.from({length: totalPosts}) // Tạo phân trang paginate({ createPage, items: items, itemsPerPage: postsPerPage, component: tagsTemplate, pathPrefix: ({ pageNumber }) => (pageNumber === 0) ? url : `${url}/page`, context: { slug: node.slug } })})// Tạo trang tác giảauthors.forEach(({ node }) => { const totalPosts = node.postCount !== null ? node.postCount : 0 // Phần này định nghĩa rằng các trang tác giả của chúng ta sẽ sử dụng // liên kết cố định `/author/:slug/`. const url = `/author/${node.slug}` const items = Array.from({length: totalPosts}) // Tạo phân trang paginate({ createPage, items: items, itemsPerPage: postsPerPage, component: authorTemplate, pathPrefix: ({ pageNumber }) => (pageNumber === 0) ? url : `${url}/page`, context: { slug: node.slug } })})// Tạo trangpages.forEach(({ node }) => { // Phần này ở đây định nghĩa rằng các trang của chúng ta sẽ sử dụng // liên kết cố định `/:slug/`. node.url = `/${node.slug}/` createPage({ path: node.url, component: pageTemplate, context: { // Dữ liệu được truyền vào context có sẵn // trong các truy vấn trang dưới dạng biến GraphQL. slug: node.slug, }, })})// Tạo trang bài đăngposts.forEach(({ node }) => { // Phần này ở đây định nghĩa rằng các bài đăng của chúng ta sẽ sử dụng // liên kết cố định `/:slug/`. node.url = `/${node.slug}/` createPage({ path: node.url, component: postTemplate, context: { // Dữ liệu được truyền vào context có sẵn // trong các truy vấn trang dưới dạng biến GraphQL. slug: node.slug, }, })})
Tại đây, chúng ta đang lặp lại lần lượt qua các thẻ, tác giả, trang và bài đăng của mình. Đối với các trang và bài đăng của mình, chúng ta chỉ cần tạo slug rồi tạo một trang mới bằng slug đó và cho Gatsby biết nên sử dụng mẫu nào.

Đối với các thẻ và trang tác giả, chúng ta cũng đang thêm thông tin phân trang bằng gatsby-awesome-pagination sẽ được truyền vào pageContext của trang.

Với điều đó, tất cả nội dung của chúng ta hiện đã được xây dựng và hiển thị thành công. Nhưng chúng ta có thể cần một chút công sức về kiểu dáng. Vì chúng ta đã sao chép trực tiếp các mẫu của mình từ Ghost Starter, nên chúng ta cũng có thể sử dụng các kiểu của chúng.

Không phải tất cả các kiểu này đều áp dụng được, nhưng để đơn giản hóa mọi thứ và không quá sa đà vào kiểu, tôi đã lấy tất cả các kiểu từ src/styles/app.css của Ghost bắt đầu từ phần Bố cục cho đến hết. Sau đó, bạn chỉ cần dán các kiểu này vào cuối tệp src/styles.css của mình.

Quan sát tất cả các kiểu bắt đầu bằng kg — đây là Koening, tên của trình chỉnh sửa Ghost. Các kiểu này rất quan trọng đối với các mẫu Bài đăng và Trang, vì chúng có các kiểu cụ thể xử lý nội dung được tạo trong trình chỉnh sửa Ghost. Các kiểu này đảm bảo rằng tất cả nội dung bạn đang viết trong trình soạn thảo của mình được dịch và hiển thị chính xác trên blog của bạn.

Cuối cùng, chúng ta cần các tệp page.jspost.js để chứa liên kết nội bộ thay thế từ bước trước, bắt đầu bằng các truy vấn:

Page.js
Mã:
ghostPage(slug: { eq: $slug } ) { ...GhostPageFields fields { html }}
Post.js
Mã:
ghostPost(slug: { eq: $slug } ) { ...GhostPostFields fields { html }}
Sau đó là các phần trong mẫu của chúng ta đang sử dụng nội dung HTML. Vì vậy, trong post.js của chúng ta, chúng ta sẽ thay đổi:
Mã:
Thành:
Mã:
Tương tự như vậy, trong tệp page.js của chúng ta, chúng ta sẽ thay đổi page.html thành page.fields.html.

Nội dung trang động​

Một trong những nhược điểm của Ghost khi được sử dụng như một CMS truyền thống là không thể chỉnh sửa từng phần nội dung trên một trang mà không cần đi vào các tệp chủ đề thực tế của bạn và mã hóa cứng nó.

Giả sử bạn có một phần trên trang web của mình là Lời kêu gọi hành động hoặc lời chứng thực của khách hàng. Nếu bạn muốn thay đổi văn bản trong các hộp này, bạn sẽ phải chỉnh sửa các tệp HTML thực tế.

Một trong những phần tuyệt vời của việc không có giao diện là chúng ta có thể tạo nội dung động trên trang web của mình mà chúng ta có thể dễ dàng chỉnh sửa bằng Ghost. Chúng ta sẽ thực hiện điều này bằng cách sử dụng các Trang mà chúng ta sẽ đánh dấu bằng thẻ 'nội bộ' hoặc thẻ bắt đầu bằng ký hiệu #.

Ví dụ, chúng ta hãy vào phần quản trị Ghost, tạo một Trang mới có tên là Tin nhắn, nhập nội dung gì đó và quan trọng nhất là chúng ta sẽ thêm thẻ #message.

Bây giờ chúng ta hãy quay lại tệp gatsby-node của mình. Hiện tại, chúng tôi đang xây dựng các trang cho tất cả các thẻ và trang của mình, nhưng nếu chúng tôi sửa đổi truy vấn GraphQL trong createPages, chúng tôi có thể loại trừ mọi thứ bên trong:
Mã:
allGhostTag(sort: { order: ASC, fields: name }, **filter: {slug: {regex: "/^((?!hash-).)*$/"}}**) { edges { node { slug url postCount } }}//...allGhostPage(sort: { order: ASC, fields: published_at }, **filter: {tags: {elemMatch: {slug: {regex: "/^((?!hash-).)*$/"}}}}**) { edges { node { slug url html } }}
Chúng tôi đang thêm bộ lọc vào các slug thẻ bằng biểu thức regex /^((?!hash-).)*$/. Biểu thức này có nghĩa là loại trừ bất kỳ slug thẻ nào bao gồm hash-.

Bây giờ, chúng ta sẽ không tạo trang cho nội dung nội bộ của mình, nhưng chúng ta vẫn có thể truy cập nội dung đó từ các truy vấn GraphQL khác. Vậy hãy thêm nó vào trang index.js của chúng ta bằng cách thêm đoạn mã sau vào truy vấn:
Mã:
query GhostIndexQuery($limit: Int!, $skip: Int!) { site { siteMetadata { title } } message: ghostPage (tags: {elemMatch: {slug: {eq: "hash-message"}}}) { fields { html } } allGhostPost( sort: { order: DESC, fields: [published_at] }, limit: $limit, skip: $skip ) { edges { node { ...GhostPostFields } } } }
Tại đây, chúng ta tạo một truy vấn mới có tên là "message" để tìm kiếm trang nội dung nội bộ của chúng ta bằng cách lọc cụ thể theo thẻ #message. Sau đó, hãy sử dụng nội dung từ trang #message của chúng ta bằng cách thêm nội dung này vào trang của chúng ta:
Mã:
//...const BlogIndex = ({ data, location, pageContext }) => { const siteTitle = data.site.siteMetadata?.title || `Title` const posts = data.allGhostPost.edges const message = data.message;//...return (    )}

Những điểm hoàn thiện​

Bây giờ chúng ta đã có một thiết lập blog thực sự tuyệt vời, nhưng chúng ta có thể thêm một vài điểm hoàn thiện cuối cùng: phân trang trên trang chỉ mục, sơ đồ trang web và nguồn cấp RSS.

Đầu tiên, để thêm phân trang, chúng ta sẽ cần chuyển đổi trang index.js của mình thành một mẫu. Tất cả những gì chúng ta cần làm là cắt và dán tệp index.js từ thư mục src/pages vào thư mục src/templates rồi thêm tệp này vào phần mà chúng ta tải các mẫu trong gatsby-node.js:
Mã:
// Tải các mẫu const indexTemplate = path.resolve(`./src/templates/index.js`)
Sau đó, chúng ta cần yêu cầu Gatsby tạo trang chỉ mục của chúng ta bằng mẫu index.js và yêu cầu nó tạo ngữ cảnh phân trang.

Chúng ta sẽ thêm toàn bộ mã này ngay sau nơi chúng ta tạo các trang bài đăng:
Mã:
// Tạo trang chỉ mục có phân trang paginate({ createPage, items: posts, itemsPerPage: postsPerPage, component: indexTemplate, pathPrefix: ({ pageNumber }) => { if (pageNumber === 0) { return `/` } else { return `/page` } }, })
Bây giờ hãy mở mẫu index.js của chúng ta và nhập thành phần Phân trang của chúng ta và thêm nó ngay bên dưới nơi chúng ta ánh xạ qua các bài đăng của mình:
Mã:
import Pagination from '../components/pagination'//...  
 //...
Sau đó, chúng ta chỉ cần thay đổi liên kết đến các bài đăng trên blog của mình từ:
Mã:
[*]
thành:
Mã:
Điều này ngăn Gatsby Link thêm tiền tố cho các liên kết của chúng ta trên các trang phân trang — nói cách khác, nếu chúng ta không làm điều này, một liên kết trên trang 2 sẽ hiển thị là /page/2/my-post/ thay vì chỉ là /my-post/ như chúng ta muốn.

Sau khi hoàn tất, hãy thiết lập nguồn cấp RSS của mình. Đây là một bước khá đơn giản, vì chúng ta có thể sử dụng một tập lệnh có sẵn từ trình khởi chạy Gatsby của nhóm Ghost. Hãy sao chép tệp generate-feed.js của họ vào thư mục src/utils của chúng ta.

Sau đó, hãy sử dụng nó trong gatsby-config.js của chúng ta bằng cách thay thế phần gatsby-plugin-feed hiện có bằng:
Mã:
{ resolve: `gatsby-plugin-feed`, options: { query: ` { allGhostSettings { edges { node { title description } } } } `, feeds: [ generateRSSFeed(config), ], },}
Chúng ta sẽ cần nhập tập lệnh của mình cùng với tệp siteConfig.js:
Mã:
const config = require(`./src/utils/siteConfig`);const generateRSSFeed = require(`./src/utils/generate-feed`);//...
Cuối cùng, chúng ta cần thêm một phần bổ sung quan trọng vào tệp generate-feed.js của mình. Ngay sau truy vấn GraphQL và trường đầu ra, chúng ta cần thêm trường tiêu đề:
Mã:
#...output: `/rss.xml`,title: "Gatsby Starter Blog RSS Feed",#...
Nếu không có trường tiêu đề này, gatsby-plugin-feed sẽ báo lỗi khi xây dựng.

Sau đó, để hoàn thiện cuối cùng, hãy thêm sơ đồ trang web bằng cách cài đặt gói gatsby-plugin-advanced-sitemap:
Mã:
npm install --save gatsby-plugin-advanced-sitemap
Và thêm nó vào tệp gatsby-config.js của chúng ta:
Mã:
{ resolve: `gatsby-plugin-advanced-sitemap`, options: { query: ` { allGhostPost { các cạnh { nút { id slug updated_at created_at feature_image } } } allGhostPage { các cạnh { nút { id slug updated_at created_at feature_image } } } allGhostTag { các cạnh { nút { id slug feature_image } } } allGhostAuthor { các cạnh { nút { id slug profile_image } } } } `, ánh xạ: { allGhostPost: { sơ đồ trang web: `bài đăng`, }, allGhostTag: { sơ đồ trang web: `thẻ`, }, allGhostAuthor: { sơ đồ trang web: `tác giả`, }, allGhostPage: { sơ đồ trang web: `trang`, }, }, loại trừ: [ `/dev-404-page`, `/404`, `/404.html`, `/offline-plugin-app-shell-fallback`, ], createLinkInHead: true, addUncaughtPages: true, }}}
Truy vấn này cũng xuất phát từ Gatsby starter của nhóm Ghost, tạo sơ đồ trang web riêng cho các trang và bài đăng cũng như trang tác giả và thẻ của chúng tôi.

Bây giờ, chúng ta chỉ cần thực hiện một thay đổi nhỏ đối với truy vấn này để loại trừ nội dung nội bộ của chúng ta. Tương tự như những gì chúng ta đã làm ở bước trước, chúng ta cần cập nhật các truy vấn này để lọc ra các slug thẻ có chứa ‘hash-’:
Mã:
allGhostPage(filter: {tags: {elemMatch: {slug: {regex: "/^((?!hash-).)*$/"}}}}) { edges { node { id slug updated_at created_at feature_image } }}allGhostTag(filter: {slug: {regex: "/^((?!hash-).)*$/"}}) { edges { node { id slug feature_image } }}

Kết thúc​

Với điều đó, bây giờ bạn đã có một blog Ghost hoạt động đầy đủ trên Gatsby mà bạn có thể tùy chỉnh từ đây. Bạn có thể tạo toàn bộ nội dung của mình bằng cách chạy Ghost trên máy chủ cục bộ và sau đó khi bạn đã sẵn sàng triển khai, bạn chỉ cần chạy:
Mã:
gatsby build
Sau đó, bạn có thể triển khai lên Netlify bằng công cụ dòng lệnh của họ:
Mã:
netlify deploy -p
Vì nội dung của bạn chỉ tồn tại trên máy cục bộ, nên bạn cũng nên sao lưu thỉnh thoảng, bạn có thể thực hiện việc này bằng tính năng xuất của Ghost.

Tính năng này sẽ xuất toàn bộ nội dung của bạn sang tệp json. Lưu ý, bài hướng dẫn này không bao gồm hình ảnh của bạn, nhưng dù sao thì chúng cũng sẽ được lưu trên đám mây nên bạn không cần phải lo lắng nhiều về việc sao lưu chúng.

Tôi hy vọng bạn thích hướng dẫn này, trong đó chúng tôi đã đề cập đến:
  • Thiết lập Ghost và Gatsby;
  • Xử lý hình ảnh Ghost bằng bộ chuyển đổi lưu trữ;
  • Chuyển đổi liên kết nội bộ Ghost thành liên kết Gatsby;
  • Thêm mẫu và kiểu cho tất cả các loại nội dung Ghost;
  • Sử dụng nội dung động được tạo trong Ghost;
  • Thiết lập nguồn cấp RSS, sơ đồ trang web và phân trang.
Nếu bạn muốn khám phá thêm những gì có thể thực hiện được với CMS không có giao diện, hãy xem công việc của tôi tại Epilocal, nơi tôi đang sử dụng một ngăn xếp công nghệ tương tự để xây dựng các công cụ cho tin tức địa phương và các nhà xuất bản trực tuyến độc lập khác.

Lưu ý: Bạn có thể tìm thấy mã đầy đủ cho dự án này trên Github tại đây và bạn cũng có thể xem bản demo đang hoạt động tại đây.

Đọc thêm​

  • “Xây dựng chủ đề Gatsby cho các trang web chạy bằng WordPress”, Paulina Hetman
  • “Xây dựng API bằng các hàm Gatsby”, Paul Scanlon
  • “Sử dụng GraphQL nâng cao trong các trang web Gatsby”, Aleem Isiaka
  • “Các hàm không cần máy chủ Gatsby và Trạm vũ trụ quốc tế”, Paul Scanlon
 
Back
Bên trên