This playground uses the following technology so you will need to check they are installed:
- Web Browser
- Code IDE (we use VS Code)
- Terminal (We use Bash)
- Node.js
Network The following ports will be used for the application and testing:
- 3000 -> React application
- 3001 -> Next.js application
The playground uses React 18 and Next.js 13. Although no existing knowledge of either will be needed for this playground it would be beneficial to have basic knowledge of Javascript.
In this playground, we'll explore React Server Components using Next.js, a leading framework for server-side web application development.
In the first part of our session, we'll dive into the emerging trend in Front-End Development, focusing on the migration of logic from the client to the server. This strategic shift addresses common challenges such as slow load times and Search Engine Optimization concerns, offering potential solutions.
In the second part, we'll delve into React Server Components and their pivotal role in transforming React into a comprehensive full-stack framework. We'll unlock the possibilities offered by Server-Side Rendering and React Server Components, showcasing how this approach enables developers to tackle the aforementioned challenges in web development.
In this section, we will explore the inner workings of a traditional React application. Our focus will narrow down to a specific timeframe within the application's lifecycle, specifically the events that unfold between a user entering a URL into the browser and the moment they witness meaningful content on the page.
We will conclude that...
In this part, we will transform a traditional React application from being client-side rendered to being server-side rendered using Next.js.
Go to: https://lab.devopsplayground.org/
This should bring up the following page:
Garb your meetup.com username. Example of meetup.com username is highlighted in yellow below (in this example, it is just the user's name and surname):
Insert your meetup.com username into the username box like in the following example. Make sure to use any capital letters as required:
Once done, click submit.
The submit button should take you to a page that looks like this:
Essentially, you should have access to a terminal and a IDE (VS Code) links.
Copy and paste the terminal link into the address bar of your browser. This should take you to the terminal that looks something like this:
As prompted, enter the following password:
Next23
Please be aware that when entering your password, it will not be visibly displayed on the screen as you type it. If you made a mistake, just refresh the page and try again.
If you have successfully logged in, your terminal should display the following message:
Run the following commands in the terminal in the following order:
-
cd workdir
-
cd DPG-Meetups-Next.js
-
npm i
-
npm run dev
If all of your commands executed correctly, your terminal should display the following outputs:
KEEPT THE TERMINAL PAGE RUNNING and open a new browser tab.
Go back to your terminal page and copy the terminal page URL. The URL should be a link ending in "devopsplayground.org:5050/wetty". For example:
In the new tab that you have opened as part of step 7 above, paste the terminal page URL that you have just copied as part of step 8. DO NOT PRESS ENTER.
Edit the URL that you have just copied into the new browser tab as part of step 9 by replacing "5050/wetty" with "3001". Your edited URL should look something like this:
Press enter and check your application is running correctly. If it is running correctly, it should look like this:
If you are having any issues, please ask for help 😊.
Go back to https://lab.devopsplayground.org/.
If you need to, enter your name and surname again into the username box.
You should be presented with the following page:
Copy the IDE link
Open a new tab and paste that link into the address bar of the new tab.
You should be presented with VS Code which should look something like this:
- In VS Code, go to the index.js file which sits inside of the pages folder:
if you are in the right file, you should be presented with this code:
import { getFeaturedEvents } from "/helpers/api";
import EventList from "./../components/events/EventList";
import { useState, useEffect } from "react";
import Loader from "/components/Loader";
export default function Homepage() {
const [featuredEvents, setFeaturedEvents] = useState([]);
useEffect(() => {
getFeaturedEvents().then((events) => {
setFeaturedEvents(events);
});
}, []);
if (!featuredEvents.length) {
return <Loader />
}
return (
<div className="Homepage">
<h1 className="main_title">Welcome to DevOps Playground Events Page</h1>
<EventList events={featuredEvents} />
</div>
);
}
- At the bottom of the file and OUTSIDE of the Homepage component, we will create a React Server Component using Next.js
getStaticProps
function. - Inside of this function, we will return an object with a key of props:
export async function getStaticProps() {
return {
props: {
featuredEvents,
}
}
}
The complete code will now look like this:
import { getFeaturedEvents } from "/helpers/api";
import EventList from "./../components/events/EventList";
import { useState, useEffect } from "react";
import Loader from "/components/Loader";
export default function Homepage() {
const [featuredEvents, setFeaturedEvents] = useState([]);
useEffect(() => {
getFeaturedEvents().then((events) => {
setFeaturedEvents(events);
});
}, []);
if (!featuredEvents.length) {
return <Loader />
}
return (
<div className="Homepage">
<h1 className="main_title">Welcome to DevOps Playground Events Page</h1>
<EventList events={featuredEvents} />
</div>
);
}
export async function getStaticProps() {
return {
props: {
featuredEvents,
}
}
}
- We will now use the same API call as we are currently using inside of the
useEffect
to fetch thefeaturedEvents
data inside ofgetStaticProps
:
export async function getStaticProps() {
const featuredEvents = await getFeaturedEvents();
return {
props: {
featuredEvents,
}
}
}
- Once we have fetched
featuredEvents
from the API inside of getStaticProps, we can go back to put Homepage component and deconstructfeaturedEvents
that we are passing from getStaticProps as props as follows:
export default function Homepage({featuredEvents}) {
...
}
- Once we have the
featuredEvent
data already in the Homepage component, we can remove any references to client side data fetching from that component. We will therefore delete:
- the
useState
hook - the
useEffect
hook - the
Loader
component - all imports that we are no longer using namely
useState
,useEffect
andLoader
Consequently, our code should look like this:
import { getFeaturedEvents } from "/helpers/api";
import EventList from "./../components/events/EventList";
export default function Homepage({featuredEvents}) {
return (
<div className="Homepage">
<h1 className="main_title">Welcome to DevOps Playground Events Page</h1>
<EventList events={featuredEvents} />
</div>
);
}
export async function getStaticProps() {
const featuredEvents = await getFeaturedEvents();
return {
props: {
featuredEvents,
}
}
}
Our running application should look like this:
- Comment out the current
getStaticProps
component or remove all of the code inside of it so it is empty for us to work with. - You can also remove the
getFeaturedEvents
import at the top of the page so the overall code looks like this:
import EventList from "./../components/events/EventList";
export default function Homepage({featuredEvents}) {
return (
<div className="Homepage">
<h1 className="main_title">Welcome to DevOps Playground Events Page</h1>
<EventList events={featuredEvents} />
</div>
);
}
export async function getStaticProps() {
}
- At the top of the page, we will import the
fs
module from node:
import fs from "fs/promises"
- We will use fs'
readFile
method inside of thegetStaticProps
and await its data as follows:
export async function getStaticProps() {
const jsonData = await fs.readFile();
}
- We will then construct the path between our current working directory and the file we are trying to read. To do that, we will import
path
from node and create a new variable which we can use to store our constructed path. - We can then ensure that the constructed path is consumed by
fs.readFile()
:
import path from "path"
....
export async function getStaticProps() {
const filePath = path.join(process.cwd(), "data", "events_data.json")
const jsonData = await fs.readFile(filePath);
}
- We can now parse the data that we received from
readFile()
using the JSON object:
export async function getStaticProps() {
const filePath = path.join(process.cwd(), "data", "events_data.json")
const jsonData = await fs.readFile(filePath);
const allEvents = JSON.parse(jsonData);
}
- Lastly, we will return the
allEvents
data using an object with a props key. Our completedgetStaticProps
function should look like this:
export async function getStaticProps() {
const filepath = path.join(process.cwd(), "data", "events", "events_data.json")
const jsonData = await fs.readFile(filepath);
const allEvents = JSON.parse(jsonData);
return {
props: {
allEvents: allEvents.events
}
}
}
- Inside of our Homepage component we now need to ensure that:
- we are destructuring the correct key (
allEvents
):
export default function Homepage({ allEvents }) {
....
}
- and we are filtering out featured events using the featured key:
export default function Homepage({allEvents}) {
const featuredEvents = allEvents.filter(event => event.featured);
return (
<div className="Homepage">
<h1 className="main_title">Welcome to DevOps Playground Events Page</h1>
<EventList events={featuredEvents} />
</div>
);
}
- the complete solution should look like this:
import EventList from "./../components/events/EventList";
import fs from "fs/promises"
import path from "path"
export default function Homepage({allEvents}) {
const featuredEvents = allEvents.filter(event => event.featured);
return (
<div className="Homepage">
<h1 className="main_title">Welcome to DevOps Playground Events Page</h1>
<EventList events={featuredEvents} />
</div>
);
}
export async function getStaticProps() {
const filePath = path.join(process.cwd(), "data", "events_data.json")
const jsonData = await fs.readFile(filePath);
const allEvents = JSON.parse(jsonData);
return {
props: {
allEvents: allEvents.events
}
}
}
- Our running application should look like this:
- Comment out the current getStaticProps component or remove all of the code inside of it so it is empty for us to work with.
- At the top of the file, we will import Mongo Client from monogodb.
import { MongoClient } from "mongodb"
- Inside of getStaticProps, we need to establish connection with MongoDB. We will use the
MongoClient
we have just imported as well as the connection string provided by MongoDB to do this. We have amended the connection string so it uses environment variables which include our credentials.
export async function getStaticProps() {
const DB_STRING = `mongodb+srv://${process.env.mongoDB_username}:${process.env.mongoDB_password}@cluster0.qonetii.mongodb.net/meetups?retryWrites=true&w=majority`;
const client = await MongoClient.connect(DB_STRING);
}
- Once we have established connection, we can connect to the database.
export async function getStaticProps() {
const DB_STRING = `mongodb+srv://${process.env.mongoDB_username}:${process.env.mongoDB_password}@cluster0.qonetii.mongodb.net/meetups?retryWrites=true&w=majority`;
const client = await MongoClient.connect(DB_STRING);
const db = client.db();
}
- We can now connect to the collection which stores our data:
export async function getStaticProps() {
const DB_STRING = `mongodb+srv://${process.env.mongoDB_username}:${process.env.mongoDB_password}@cluster0.qonetii.mongodb.net/meetups?retryWrites=true&w=majority`;
const client = await MongoClient.connect(DB_STRING);
const db = client.db();
const meetupCollection = db.collection("meetups");
}
- Finally, we can retrieve data from our collection and turn it into an array for us to work with as follows:
export async function getStaticProps() {
const DB_STRING = `mongodb+srv://${process.env.mongoDB_username}:${process.env.mongoDB_password}@cluster0.qonetii.mongodb.net/meetups?retryWrites=true&w=majority`;
const client = await MongoClient.connect(DB_STRING);
const db = client.db();
const meetupCollection = db.collection("meetups");
const events = await meetupCollection.find().toArray();
}
- Don't forget to close connection to your database once the data has been retrieved:
export async function getStaticProps() {
const DB_STRING = `mongodb+srv://${process.env.mongoDB_username}:${process.env.mongoDB_password}@cluster0.qonetii.mongodb.net/meetups?retryWrites=true&w=majority`;
const client = await MongoClient.connect(DB_STRING);
const db = client.db();
const meetupCollection = db.collection("meetups");
const events = await meetupCollection.find().toArray();
client.close();
}
- Now we can return the retrieve data:
export async function getStaticProps() {
const DB_STRING = `mongodb+srv://${process.env.mongoDB_username}:${process.env.mongoDB_password}@cluster0.qonetii.mongodb.net/meetups?retryWrites=true&w=majority`;
const client = await MongoClient.connect(DB_STRING);
const db = client.db();
const meetupCollection = db.collection("meetups");
const events = await meetupCollection.find().toArray();
client.close();
return {
props: {
allEvents: events.map((event) => ({
date: event.date,
description: event.description,
featured: event.featured,
location: event.location,
presenters: event.presenters,
title: event.title,
id: event._id.toString(),
})),
}
};
}
- The complete solution should look like this:
import EventList from "./../components/events/EventList";
import { MongoClient } from "mongodb"
export default function Homepage({allEvents}) {
const featuredEvents = allEvents.filter(event => event.featured);
return (
<div className="Homepage">
<h1 className="main_title">Welcome to DevOps Playground Events Page</h1>
<EventList events={featuredEvents} />
</div>
);
}
export async function getStaticProps() {
const DB_STRING = `mongodb+srv://${process.env.mongoDB_username}:${process.env.mongoDB_password}@cluster0.qonetii.mongodb.net/meetups?retryWrites=true&w=majority`;
const client = await MongoClient.connect(DB_STRING);
const db = client.db();
const meetupCollection = db.collection("meetups");
const events = await meetupCollection.find().toArray();
client.close();
return {
props: {
allEvents: events.map((event) => ({
date: event.date,
description: event.description,
featured: event.featured,
location: event.location,
presenters: event.presenters,
title: event.title,
id: event._id.toString(),
})),
}
};
}
- The application should now look like this: