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!
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:
Hoặc bạn có thể bắt đầu từ mã trong kho lưu trữ Github này.
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-s3 và ghost-s3-compat. Tôi sử dụng
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:
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 đó:
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:
Sau đó, chúng ta chỉ cần thêm nội dung sau:
Các giá trị cho
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)
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
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à
Sau đó, chúng ta sẽ thêm nội dung sau vào tệp
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:
Trong tệp
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
Trong
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
Và với điều đó, chúng ta chỉ cần chỉnh sửa phần này trong mẫu:
Để là:
Đ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à
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:
Và thêm
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:
Sau đó, chúng tôi lấy các thành phần
Tệp
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
Các gói mà chúng ta cần cài đặt là gatsby-awesome-pagination,
Vì vậy, chúng ta sẽ thực hiện:
Sau đó, chúng ta cần thực hiện một số cập nhật cho Tệp
Đầ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:
Tiếp theo, trong
Đ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:
Sau đó, chúng tôi sẽ tải tất cả các mẫu của mình:
Lưu ý ở đây là chúng ta đang thay thế mẫu
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:
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
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
Quan sát tất cả các kiểu bắt đầu bằng
Cuối cùng, chúng ta cần các tệp
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
Thành:
Tương tự như vậy, trong tệp
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ẻ
Bây giờ chúng ta hãy quay lại tệp
Chúng tôi đang thêm bộ lọc vào các slug thẻ bằng biểu thức regex
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
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ẻ
Đầu tiên, để thêm phân trang, chúng ta sẽ cần chuyển đổi trang
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
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:
Bây giờ hãy mở mẫu
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ừ:
thành:
Đ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à
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
Sau đó, hãy sử dụng nó trong
Chúng ta sẽ cần nhập tập lệnh của mình cùng với tệp
Cuối cùng, chúng ta cần thêm một phần bổ sung quan trọng vào tệp
Nếu không có trường tiêu đề này,
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
Và thêm nó vào tệp
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-’:
Sau đó, bạn có thể triển khai lên Netlify bằng công cụ dòng lệnh của họ:
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:
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.
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ànhgatsby-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
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-s3 và ghost-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”
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/*" } ]}
Để 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" }}
accessKeyId
và secretAccessKey
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;
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 }) }}
Mã:
npm install --save url cheerio
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 }}
Mã:
Mã:
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
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ụcsrc/components/meta
) - Các tệp thành phần Pagination.js và PostCard.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ụcsrc/utils
của chúng: fragments.js và siteConfig.js - Và các mẫu sau từ thư mục
src/templates
của chúng: tag.js, page.js, author.js và post.js
Sau đó, chúng tôi lấy các thành phần
Phân trang
và PostCard.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
và @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
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`);
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 } } }}
Để 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
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`)
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, }, })})
Đố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.js
và post.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 }}
post.js
của chúng ta, chúng ta sẽ thay đổi:
Mã:
Mã:
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 } }}
/^((?!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 } } } }
#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`)
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` } }, })
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'//...
//...
Mã:
[*]
Mã:
/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), ], },}
siteConfig.js
:
Mã:
const config = require(`./src/utils/siteConfig`);const generateRSSFeed = require(`./src/utils/generate-feed`);//...
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",#...
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
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, }}}
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
Mã:
netlify deploy -p
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.
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