Interactive Content in a Ghost Blog Post using React
Ghost's simplicitly as a blogging platform is great, but sometimes you need some custom logic on a page - this post shows an approach using React and AWS Lambda.
Introduction
For a blog, Ghost can be great - it's nice and simple to use and the editor is elegant and fairly powerful.
Unlike some, it has not been augmented/screwed up/polluted (depending on your view) by a noisy and risky plugin ecosystem that would allow it to be all things to all people, but also would introduce latency, complexity, vulnerabilities and likely other challenges.
The downside to the lack of plugins and the clean but limited approach is that there is no built in mechanism for creating richer or more complex pages, for example a contact form.
Other blogging platforms usually achieve these through plugins which largely consist of custom code that can do a wide variety of good, or bad things.
Demonstration
The following demo shows a React App embedded in the page, and making a remote call that returns real time data:
Refreshing this page will re-render and get the updated time from the server (note it will be in UTC regardless of where you view from).
React in Ghost
When React renders it takes an element, usually a div with the ID of root and uses that as the frame in which to render - this can be a whole page, like the sample react app, or part of a page.
In the sample React App, the index.html
page that is served up by the development process contains:
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
And in turn, index.js
or index.tsx
binds to the element with:
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
We can use this behaviour to add a React App into part of a Ghost blog post or page.
When a React app is built it generates a javascript bundle, css file and html page - in a basic setup you can host all of these with a static web server, or something like AWS S3 - the index.html served up will load the javascript and css and the App will render on page load.
<script defer="defer" src="https://static.blockdev.io/demo/static/js/main.js">
</script>
<noscript>
You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
The demo above uses an id of demo
rather than root
.
Complex Interactions
This concept is great for creating rich and complex local experiences, but it is also particularly useful for creating more complex interactions and those that make remote (API/REST) calls, eg a contact form.
Creating a lambda
The easiest way to create a basic lambda function is via the AWS console:
And the use the in-line code editor to edit the function and encapsulate some logic:
To make it accessible via a web request we can Create a function URL which is under the Configuration tab (select NONE for Auth Type when asked):
To allow the function to be called from a browser enable the CORS configuration and set an approprate origin. The default * will work for testing just beware that anything can call it. Also note this setting does not stop direct requests from the address bar or tools like curl, wget or postman:
WAF rules through an API gateway or cloudfront distribution could be used to protect against malicious invocations.
Now we have a URL:
Visiting it in the browser will invoke the function and render the result:
Updating the App
The lambda's output can be consumed in the app as follows which is effectively what the demo above does.
import React, { useEffect } from 'react';
import './App.css';
function App() {
const [text, setText] = React.useState('Loading...');
useEffect(() => {
fetch('https://tiqge3immgd275bkwdp5yvfdkm0dvxlk.lambda-url.eu-west-2.on.aws/')
.then(response => response.text())
.then(data => {
setText(data);
})
.catch(console.error);
}, []);
return (
<div className="App">
<header className="App-header">
{text}
</header>
</div>
);
}
export default App;
Conclusion
Dropping HTML and Javascript into a page is a longstanding way of injecting logical and custom content, be it external or otherwise.
Combined with React this can create a nice embedded, self-contained application or experience. It can also create a richer experience by making remote calls to third party services and/or custom logic hosted on a server or somewhere like AWS Lambda.