The first part of this blog development diary left with the back end being completed, using a combination of Google Drive As the database comma Google docs as the word processing unit and Google app script as the back end server providing restful API performance. This part we'll focus on the front end and fetching the data via the rest of API and then parsing it into content for displaying on the website.
At the start of my coding journey I spent a lot of time working with regular JavaScript jQuery and python, I always preferred Python due to its clean syntax and I felt the separation of JavaScript code with HTML to be awkward. Clearly I was not alone in this feeling as the people at Facebook (now Meta) devised a JavaScript library known as react which allowed for HTML snippets to be inserted into JavaScript and to be written on one page if required. I started learning React about eight months ago, and I find it matches with how I think about coding problems compared to regular JavaScript, and I very much enjoy working with it.
That said, React has several limitations, and although the team are working on dealing with those limitations comma another team has also approached those limitations fire combining react style coding with a node JS Style backend comma creating a potential all the one solution that is a true framework unlike react. This framework is known and as nextJS
I spent a fair amount of time working with next JS, and I decided to use this blog as my launch platform to try to understand multiple aspects of next, and how it differs to react.
There are several features of next year that are potentially useful in a Blog Page, although I have opted not to use all of them in this project as it was my first. Nextjs is often used because of its strong server-side rendering and static site generation features, which react is generally incapable of producing. Additionally it has a number of server components that allow the use of oAuth2.0 identification, which react can use but will give away passwords to the client so if something like a droid service is required then react cannot do this securely. I have blogged about potential differences between using React and nextJS for Google Sheet fetch requests previously.
For this project I opted to use next as with its new app router, a feature of next 13.
Starting a nextjs project is relatively straightforward, there are npm packages that can be used to install next and I use Visual Studio code as my code editor, and gitHub as my code repository and version control.
I started this project by opening my next JS projects folder in Visual Studio code and then creating an next application with the npm
npx create-next-app@latest
I created an application with the new app router, JavaScript, and eslint. I opted not to use typescript for speed and relative simplicity of the project.
when in the project I created a Localhost using the command npm run dev.
Mentioned that react allows you to reduce everything on a single page, I am not necessarily a fan of One page Solutions. I try to commit to clean code principles, and when doing this with next applications I like to separate UI layout from components.
Thus my landing page is often relatively simplistic from a coding perspective, and this project is no exception.
Using a quick wireframe SketchUp of the areas that I wish to have in my blog, and using standard coding practices of starting with a mobile site design and moving up to a desktop. My design setup included a navbar at top, and the author detail at the bottom of a header and a list of blogs. When in tablet mode, the author detail is to move to the right of the page to sit alongside the list of blogs, while in desktop mode an additional component indexing the blogs in a more concise manner will be present to the left of the blog list, leaving the blog in the centre.
Why frame layout for responsive UI design of the blog landing page.After devising the responsive layout, it became pretty obvious that a CSS grid would be the ideal method for achieving the component layout design. Both the blog list and the blog appendix will need the fetch URL comma and in order to continue with the clean code principles of only ever declaring a variable once, it is therefore important to declare the URL for the fetch here. Because of how nextjs is constructed repeat fetch requests in different components are unified into a single fetch request individual fetches can be produced for the blog list and block sidebar, as long as I use the same URL referenced here
This becomes the layout of the landing page:
function App() {
const url =
"https://script.google.com/macros/s/AKfycbxyp7K9EL7OtNvxdVHYursHvm-DxRnMLAPp7R23m2sXObx4vWnmqRJXj_GddNNKgJHE/exec";
return (
<>
<BlogNavBar />
<div className="blog-grid">
<BlogHeader />
<BlogList url={url} />
<BlogSideBar url={url} />
<BlogAuthor />
</div>
</>
);
}
This then gives me 5 component pages to build.
BlogNavBar, BlogHeader and Blog Author
I'm not planning on going into detail of the design of the blog nav bar, blog header or blog author, if you're interested you can look at the source code. These are relatively straightforward components that you can find on many websites, the navbar merely navigates to my homepage or the blog list, the header is essentially a hero section, with a royalty free image and some text, the blog author section just shows some information about me.
Aside, WebFetch
The BlogList, BlogSidebar and individual blog entries when loaded all require data to be passed from the back end server via RESTful transfer, in this case a JSON file. The fetch request is the same for both requests with only the URL being different and the individual blog article Having an ID tagged onto the end of the fetch url. As such, to continue commitment to clean code and put this function in a separate component that is called by all components that require it. as I mentioned previously next JS collate all API calls so there are not repeated calls even if multiple individual components have the same fetch request in them.
export default async function googleWebFetch(url) {
const response = await fetch(url, { cache: 'no-store' });
const data = await response.json();
return data;
}
BlogList
The BlogList component contains a list of blog links, what I want to display is the data from the first fetch from the back end. The data that is being fetched is:
{
"title": "Making This Blog",
"image": {
"imageDescription": "Googles suite is easy to use and powerful",
"imageCuri": "https://lh3.googleusercontent.com/c81hOQHheIyFMA4Fp_j68ZMbWS7RQWhu0ljnhirBneNNiOMuV1BIz86zftTtuNGyNKbC6IXBEI2iA6rZrwddCNp0pLMTJNDfXxGkDVzx-knFlWZ7PXRL_4QawazFHBcD9dp_Z74B5MKBROABx-f2-dtLWDqUZ11h"
},
"introText": "So one thing that a lot of coders do is write a blog. While I’m not normally a trend chaser, I do see the benefit of doing something like a blog to show your inner thoughts on coding and the like.\n",
"fileId": "1VBdL5h6a8hiY7C3S3xBNrvElvBCpGQEwZQ8kEZusGhY",
"fileDate": "2023-07-21T18:51:45.348Z"
},
And this can be used almost in its entirety to create the component.
export default function blogList(props) {
const url = props.url
const data = use(
googleWebFetch(url)
);
const blogs = [];
if (data !== "undefined") {
for (let blog in data) {
const date = new Date(data[blog].fileDate).toDateString();
let returnValue = (
<Link href={`/blog/${encodeURIComponent(data[blog].fileId)}`} className="blog-list-wrapper">
<div className="blog-list-wrapper">
<h1 className="blog-list-title">{data[blog].title}</h1>
<p className="blog-list-date">{date}</p>
<img
className="blog-list-image"
src={data[blog].image.imageCuri}
referrerPolicy="no-referrer"
/>
<p className="blog-list-text fade">{data[blog].introText}</p>
</div>
</Link>
);
blogs.push(returnValue);
}
}
return <div className="blog-list">{blogs}</div>;
}
BlogSideBar
Component is very similar to the blog list component, only the link doesn't utilise an image because delicious designed to be more concise.
export default function BlogSideBar(props) {
const url = props.url;
const data = use(googleWebFetch(url));
const blogs = [];
for (let blog in data) {
const date = data[blog].fileDate.toString().slice(0, 10);
const title = data[blog].title;
const fileId = data[blog].fileId;
let returnValue = (
<Link
href={`/blog/${encodeURIComponent(fileId)}`}
className="sidebar-wrapper"
key={`blog-${blog}`}
>
<p className="sidebar-title sidebar-text">{title}</p>
<p className="sidebar-date sidebar-text">{date}</p>
</Link>
);
blogs.push(returnValue);
}
return (
<div className="blog-sidebar">
<div className="blog-sidebar-header">
<h1>Index</h1>
</div>
<div>{blogs}</div>
</div>
);
}
Should be noted that in certain page layouts the BlogSideBar is not presented and will be hidden. This includes mobile and tablet forms.
The last part of the front end project is the individual blog pages full stop one of the reasons for utilizing nextjs is its dynamic routing allowing for these pages to be created when accessed comma meaning that the front end doesn't need to be updated every time a new blog is dropped into the database comma in this case Google Drive. fetch requests are conducted on page access go this is partly to make sure that information is as up to date as possible, but also because the images I've been pulled directly from the docs file, and these links have a limited lifetime.
The id of the page is the unique file ID on the Google Drive, this was just done to make life simple, and matches how Google is itself works.
Again committing to clean code principles, the Core page.jsx handles the fetch request and then is used for UI design only. The same blog author component is reused, as is the blog header and Navbar, giving the [id] page.jsx the following code.
export default function Page ({ params }) {
// datafetch
const url =
"https://script.google.com/macros/s/AKfycbzL_nbz0E1J96OUBanb2dD9WGJctFBcThK8SmD675YEWKxTVt9Nx3ZkTeiDMSWVa6YA8Q/exec";
const fileId = params.id;
const fullUrl = `${url}?id=${fileId}`;
const data = use(googleWebFetch(fullUrl));
const blogs = data.map((blogElement) => {
return (
<>
<BlogParse {...blogElement} />
</>
);
});
return (
<>
<Navbar />
<div className="blog-grid">
<BlogHeader />
<div className="blog-wrapper">{blogs}</div>
<BlogAuthor />
</div>
</>
);
}
BlogParse
As the blog parse is the only unique Component it's the only one I'll discuss in this page.
The purpose of this component is to take the json file received from the fetch request combat and convert it into HTML that can then be displayed on the website. The json file received is an array of objects, ordered the same way As the docs file. By iterating through the array, taking each object and converting it into an appropriate HTML code block the blog parse function Create react elements that can be slaughtered into an array that can then be displayed on the main page.
Selecting the appropriate tag
The first small sub function is parseTag, which simply reads the style that has been presented and create a HTML appropriate tag. if for whatever reason Either no tag or an expected tag has been detected then it will default to a p tag.
const parseTag = (style) => {
return style === "HEADING_1"
? "h1"
: style === "HEADING_2"
? "h2"
: style === "HEADING_3"
? "h3"
: style === "SUBTITLE"
? "legend"
: style === "CODE"
? "code"
: "p";
};
The next sub function is relatively long, the parseElement function looks at the tag that has been passed along with the data. this will form the className For styling, but also because different HTML components required different layouts, the ones that do require specific layouts are selected by if class name and constructed as needed. there are two tags for images because as potential for inline as well as solo images. after this links Are detected, finally code blocks detected and they run through a separate library prismJS.
const parseElement = (num, tag, key) => {
// image
if (tag === "blog-solo-image") {
return (
<img
className="blog-solo-image"
src={item.content[num].imageCuri}
alt={item.content[num].imageDescription}
referrerPolicy="no-referrer"
key={key}
/>
);
}
if (item.className[num] === "blog-image") {
return (
<img
className="blog-inline-image"
src={item.content[num].imageCuri}
alt={item.content[num].imageDescription}
referrerPolicy="no-referrer"
key={key}
/>
);
}
// link
if (item.className[num] === "link") {
return (
<a
className="blog-link"
href={item.content[num].link}
target="_blank"
key={key}
>
{item.content[num].text}
</a>
);
}
// code block
if (item.className[num] === "blog-code-block") {
const codeBlockContent = item.content[num].join("")
return (
<div className="code-block" key={key}>
<PrismBlock code={codeBlockContent} />
</div>
);
}
Remaining HTML elements
The remaining elements are structured the same way, so while I could continue to create if chains forbold, italics Etc, you can just create a single component that Returns generics. while putting a varied Tag into a react return will create an error, these tags are actually syntaxic sugar and can be mimicked by just using the actual raw request which is React.createElement. This reduces the amount of required coding.
item.className[num] === "bold"
? (tag = "strong")
: item.className[num] === "italics"
? (tag = "i")
: (tag = tag);
let className = item.className[num];
// specific coding to add legend className to picture legends.
if (tag === "legend") {
className = tag;
tag = "p";
}
// generic return element h1, h2, h3, etc
return React.createElement(
tag,
{ className: className, key: key },
item.content[num]
);
};
The final part of the blogParser checks to see if the element is A single element or a multi block, such as a line of text plus a link or bold text. If there's multiple elements then the passer links them all together via spans.
// add single component element
if (item.className.length === 1) {
let tag = parseTag(item.style);
if (item.className[0] === "blog-image") {
tag = "blog-solo-image";
}
return parseElement(0, tag);
}
const contentList = [];
for (let i = 0; i < item.className.length; i++) {
let tag = "span";
contentList.push(parseElement(i, tag, i));
}
// add multi component element
let tag = parseTag(item.style);
return React.createElement(
tag,
{ className: "blog-multi-element-container" },
contentList
);
}
And that's it really, the resulting code will parse the data that has been presented to it and display as per the CSS file.
The construction of this blog represented my first experiences with both Google Apps Script as a node js back end providing content management service that provided restful API structured data sent to a front end that was designed using the nextJS framework.
I was able to successfully create a web page that displayed the content of a Google Docs file, and developed a system that allowed me to just write Google Docs files, drop them into the appropriate Google Drive directory, and have them displayed on my website.
While this blog is server-side rendered comma with the exception of the code block which is styled on the client side due to how prism JS functions with react, there are many features that next JS has such as static site generation that I did not utilise full stop the principle reasons for this is to do with the short lifespan of the image links generated by Google. If I was to do this again I would most likely develop a system that extracted the images out of the docs file and place them in a directory that was accessible for more permanent linking. alternatively I would just use another back end as there are many that are suitable for running blogs.
I feel that this project is given me a good understanding of linking front end and backend together, as well as creating a full stack Project that works as intended, they conclusion of this project is a relatively easy to use block generation system that employees a highly popular and very powerful word processing unit and Google Docs as the heart of its content delivery system. Google app script x as a middle manager fetching the drive data and providing tailored information to the client for stop only information that is required to be sent for the successful construction of a Blog is sent, making the json requests more efficient than an API fetch.