🔧 Programmierung 🕛 vor 1 Jahr 14 Min Lesezeit
0

Epic Next.js 15 Tutorial Part 2: Building Out The Home Page

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

This post is part 2 of many in our Epic Next.js Series. You can find the outline for upcoming posts here.












In this post, we will start by building our home page. We will focus on the Hero Component and Features Component and use Dynamic Zone to allow our Strapi admins to choose which component they want to use.



.



Once our data is returned by our Strapi API, we will build out those same components within our Next JS app.



Our goal is to display our Hero Section and Features Sections on our Next application. So let's get started.






Structuring Our Data In Strapi



In Strapi, there are many ways of structuring your data; you can create single types, collection types, and components that allow you to create reusable content types that you can use in multiple places.



We will build our Hero Section and Features Section as components.



Let's start by building out our Hero Section Component.






Building The Hero Section Component



Looking at our Hero Section UI, we can break it down into the following parts.





We will create the following fields.



Media -> Single Media - image

Text -> Short Text - heading

Text -> Long Text - subHeading



Note: Change it to only allow images for media in advanced settings.





Our Link component will have the following fields.

Text -> Short Text -> url

Text -> Short Text -> text

Boolean -> isExternal



Note: for isExternal in the advanced setting, change the default value to be set to false.



Let's go ahead and add them now.





The completed fields in our Hero Section component should look like the following:





We can accomplish this by going to Content-Type Builder, selecting the Home Page under SINGLE TYPES and clicking on Add another field to this single type.



Select the Dynamic Zone field, give it a name called blocks, and click Add components to the zone.



Finally, select Use an existing component and choose our Hero Section component.



Great, we now have our first component that has been added to our Home Page



Before creating our Features Section component, let's see if we can get our current component from our API.





Fetching The Hero Section Component Data



First, let's add some data.





Now, let's test our API call in Postman. But before we do, we need to specify in Strapi all the items we would like to populate.



Looking at our content, we need to populate the following items: blocks, image, and link.



.



Remember, we can construct our query using the



http://localhost:1337/api/home-page?populate[blocks][on][layout.hero-section][populate][image][fields][0]=url&populate[blocks][on][layout.hero-section][populate][image][fields][1]=alternativeText&populate[blocks][on][layout.hero-section][populate][link][populate]=true



Here is the



We will get the following data after making a GET request in Postman.




CODE
{
"data": {
"id": 3,
"documentId": "fcnlk9xwoqmogfxvfim713y4",
"title": "Home Page",
"description": "This is our first single type",
"createdAt": "2024-10-01T18:33:35.081Z",
"updatedAt": "2024-10-01T23:15:19.426Z",
"publishedAt": "2024-10-01T23:15:19.436Z",
"locale": null,
"blocks": [
{
"__component": "layout.hero-section",
"id": 2,
"heading": "Epic Next.js Tutorial",
"subHeading": "It's awesome just like you.",
"image": {
"id": 1,
"documentId": "fzwtij74oqqf9yasu9mit953",
"url": "/uploads/computer_working_3aee40bab7.jpg",
"alternativeText": null
},
"link": {
"id": 2,
"url": "/login",
"text": "Login",
"isExternal": false
}
}
]
},
"meta": {}
}






Now that we know our Strapi API works, let's move into the frontend of our project, fetch our new data, and create our Hero Section React component.






Fetching Our Home Page Data In The Frontend



Taking a look at our frontend code, this is what we have so far.




CODE
async function getStrapiData(url: string) {
const baseUrl = "http://localhost:1337";
try {
const response = await fetch(baseUrl + url);
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}

export default async function Home() {
const strapiData = await getStrapiData("/api/home-page");

const { title, description } = strapiData.data;

return (
<main className="container mx-auto py-6">
<h1 className="text-5xl font-bold">{title}</h1>
<p className="text-xl mt-4">{description}</p>
</main>
);
}






If we console log strapiData, we will notice that we are not yet getting all our fields.




CODE
{
data: {
id: 2,
documentId: 'fcnlk9xwoqmogfxvfim713y4',
title: 'Home Page',
description: 'This is our first single type',
createdAt: '2024-10-01T18:33:35.081Z',
updatedAt: '2024-10-01T18:33:35.081Z',
publishedAt: '2024-10-01T18:33:35.090Z',
locale: null
},
meta: {}
}






That is because we need to tell Strapi what items we would like to populate. We already know how to do this; it is what we did earlier in the section when using Strapi Query Builder.



We will use the query that we defined previously.




CODE
{
populate: {
blocks: {
on: {
"layout.hero-section": {
populate: {
image: {
fields: ["url", "alternativeText"]
},
link: {
populate: true
}
}
}
}
}
},
}






But to make this work, we must first install the qs package from NPM. You can learn more about it .



Our updated function will look like the following.




CODE
async function getStrapiData(path: string) {
const baseUrl = "http://localhost:1337";

const url = new URL(path, baseUrl);
url.search = homePageQuery;

try {
const response = await fetch(url.href);
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}






The final code will look as follows:




CODE
import qs from "qs";

const homePageQuery = qs.stringify({
populate: {
blocks: {
on: {
"layout.hero-section": {
populate: {
image: {
fields: ["url", "alternativeText"]
},
link: {
populate: true
}
}
}
}
}
},
});

async function getStrapiData(path: string) {
const baseUrl = "http://localhost:1337";

const url = new URL(path, baseUrl);
url.search = homePageQuery;

try {
const response = await fetch(url.href);
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}

export default async function Home() {
const strapiData = await getStrapiData("/api/home-page");

console.dir(strapiData, { depth: null });

const { title, description } = strapiData.data;

return (
<main className="container mx-auto py-6">
<h1 className="text-5xl font-bold">{title}</h1>
<p className="text-xl mt-4">{description}</p>
</main>
);
}






When we look at our response in the terminal, we should see that Strapi has returned all of our requested data.




CODE
{
data: {
id: 3,
documentId: 'fcnlk9xwoqmogfxvfim713y4',
title: 'Home Page',
description: 'This is our first single type',
createdAt: '2024-10-01T18:33:35.081Z',
updatedAt: '2024-10-01T23:15:19.426Z',
publishedAt: '2024-10-01T23:15:19.436Z',
locale: null,
blocks: [
{
__component: 'layout.hero-section',
id: 2,
heading: 'Epic Next.js Tutorial',
subHeading: "It's awesome just like you.",
image: {
id: 1,
documentId: 'fzwtij74oqqf9yasu9mit953',
url: '/uploads/computer_working_3aee40bab7.jpg',
alternativeText: null
},
link: { id: 2, url: '/login', text: 'Login', isExternal: false }
}
]
},
meta: {}
}









Let's Create Our Hero Section Component



Before we can render our section data, let's create our Hero Section component.



We will start with the basics to ensure that we can display our data, and then we will style the UI with Tailwind and Shadcn.



Inside the components let's create a new folder called custom, this is where we will add all of our components that we will create.



Inside the custom folder let's create a bare component called hero-section.tsx.



We will add the following code to get started.




CODE
export function HeroSection({ data } : { readonly data: any }) {
console.dir(data, { depth: null })
return (
<div>Hero Section</div>
)
}






We will update the types later, but for now we will do the I don't know TS and just use any;



Now that we have our basic component, let's import in our home page.




CODE
import qs from "qs";
import { HeroSection } from "@/components/custom/hero-section";

const homePageQuery = qs.stringify({
populate: {
blocks: {
on: {
"layout.hero-section": {
populate: {
image: {
fields: ["url", "alternativeText"]
},
link: {
populate: true
}
}
}
}
}
},
});

async function getStrapiData(path: string) {
const baseUrl = "http://localhost:1337";

const url = new URL(path, baseUrl);
url.search = homePageQuery;

try {
const response = await fetch(url.href);
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}

export default async function Home() {
const strapiData = await getStrapiData("/api/home-page");

console.dir(strapiData, { depth: null });

const { title, description, blocks } = strapiData.data;

return (
<main className="container mx-auto py-6">
<h1 className="text-5xl font-bold">{title}</h1>
<p className="text-xl mt-4">{description}</p>
<HeroSection data={blocks} />
</main>
);
}






We should see the following output when running our app. Notice we are able to see our HeroSection component.





And the final code in the page.tsx file should reflect the following changes.




CODE
import qs from "qs";
import { HeroSection } from "@/components/custom/hero-section";

const homePageQuery = qs.stringify({
populate: {
blocks: {
on: {
"layout.hero-section": {
populate: {
image: {
fields: ["url", "alternativeText"],
},
link: {
populate: true,
},
},
},
},
},
},
});

async function getStrapiData(path: string) {
const baseUrl = "http://localhost:1337";

const url = new URL(path, baseUrl);
url.search = homePageQuery;

try {
const response = await fetch(url.href);
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}

export default async function Home() {
const strapiData = await getStrapiData("/api/home-page");

console.dir(strapiData, { depth: null });

const { blocks } = strapiData.data;

return (
<main>
<HeroSection data={blocks[0]} />
</main>
);
}










Continue Working On Our Hero Section Component



Going back to the hero-section.tsx file, let's go ahead and display our Strapi data.



This is what our data looks like.




CODE
{
__component: 'layout.hero-section',
id: 2,
heading: 'Epic Next.js Tutorial',
subHeading: "It's awesome just like you.",
image: {
id: 1,
documentId: 'fzwtij74oqqf9yasu9mit953',
url: '/uploads/computer_working_3aee40bab7.jpg',
alternativeText: null
},
link: { id: 2, url: '/login', text: 'Login', isExternal: false }
}







Let's use this response structure do define our interface.



Let's create the following interfaces.




CODE
interface Image {
id: number;
documentId: string;
url: string;
alternativeText: string | null;
}

interface Link {
id: number;
url: string;
label: string;
}

interface HeroSectionProps {
id: number;
documentId: string;
__component: string;
heading: string;
subHeading: string;
image: Image;
link: Link;
}






And update our HeroSection component use our types.




CODE
export function HeroSection({ data }: { readonly data: HeroSectionProps }) {
// ...rest of the code
}







Finally, let's add our data from Strapi instead of hard coding it by making the following changes to our HeroSection component.




CODE
import Link from "next/link";

interface Image {
id: number;
documentId: string;
url: string;
alternativeText: string | null;
}

interface Link {
id: number;
url: string;
text: string;
}

interface HeroSectionProps {
id: number;
documentId: string;
__component: string;
heading: string;
subHeading: string;
image: Image;
link: Link;
}

export function HeroSection({ data }: { readonly data: HeroSectionProps }) {
console.dir(data, { depth: null });
const { heading, subHeading, image, link } = data;
const imageURL = "http://localhost:1337" + image.url;

return (
<header className="relative h-[600px] overflow-hidden">
<img
alt={image.alternativeText ?? "no alternative text"}
className="absolute inset-0 object-cover w-full h-full"
height={1080}
src={imageURL}
style={{
aspectRatio: "1920/1080",
objectFit: "cover",
}}
width={1920}
/>
<div className="relative z-10 flex flex-col items-center justify-center h-full text-center text-white bg-black bg-opacity-40">
<h1 className="text-4xl font-bold md:text-5xl lg:text-6xl">
{heading}
</h1>
<p className="mt-4 text-lg md:text-xl lg:text-2xl">
{subHeading}
</p>
<Link
className="mt-8 inline-flex items-center justify-center px-6 py-3 text-base font-medium text-black bg-white rounded-md shadow hover:bg-gray-100"
href={link.url}
>
{link.text}
</Link>
</div>
</header>
);
}






We are now getting and displaying our data from Strapi but here are still more improvements that we must make in this component.



" path that we append to our image url but instead should get it from our .env variable.



We will do this in the next post, where we will finish up our Hero Section component and start working on the Features Component






Conclusion



We are making some great progress. In this post, we started working on displaying our Hero Section content.



In the next post, we will create the StrapiImage component using the Next Image component to make rendering Strapi images easier.



Finish up our Hero Section and start working on our Features Section.



I hope you are enjoying this series so far. Thank you for your time, and I will see you in the next one.






Note about this project



This project has been updated to use Next.js 15 and Strapi 5.



If you have any questions, feel free to stop by at our .



You can also find the blog post content in the Strapi Blog.



Feel free to make PRs to fix any issues you find in the project or let me know if you have any questions.



Happy coding!




  • Paul

Vollständiger Original-Artikel
Den kompletten Beitrag mit allen Details direkt auf dev.to lesen.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
CVE-2024-45058 | portabilis i-educar up to 2.8 Setting educar_usuario_cad.php authorization
1 Quelle
Kompakte 10.000-mAh-Powerbank für weniger als 10 Euro bei Amazon Haul
1 Quelle
Bessere Grafik in Spielen: So steigern Sie die Bildqualität ohne FPS-Verlust
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Epic Next.js 15 Tutorial Part 2: Building Out The Home Page

Thematisch verwandte Begriffe: Epic, Nextjs, Tutorial, Part · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...