Πώς να δημιουργήσετε μια δυναμική εφαρμογή Web Rick και Morty Wiki με το Next.js

Η δημιουργία εφαρμογών ιστού με δυναμικά API και απόδοση από πλευράς διακομιστή είναι ένας τρόπος για να προσφέρετε στους ανθρώπους μια υπέροχη εμπειρία τόσο με περιεχόμενο όσο και με ταχύτητα. Πώς μπορούμε να χρησιμοποιήσουμε το Next.js για να δημιουργήσουμε εύκολα αυτές τις εφαρμογές;

  • Τι πρόκειται να οικοδομήσουμε;
  • Τι είναι το Next.js;
  • Βήμα 0: Ρύθμιση μιας νέας εφαρμογής Next.js
  • Βήμα 1: Ανάκτηση χαρακτήρων Rick και Morty με ένα API στο Next.js
  • Βήμα 2: Εμφάνιση χαρακτήρων Rick και Morty στη σελίδα
  • Βήμα 3: Φόρτωση περισσότερων χαρακτήρων Rick και Morty
  • Βήμα 4: Προσθήκη της δυνατότητας αναζήτησης χαρακτήρων Rick και Morty
  • Βήμα 5: Χρήση δυναμικών διαδρομών για σύνδεση σε σελίδες χαρακτήρων Rick και Morty
  • Βήμα μπόνους: Αναπτύξτε το wiki Rick και Morty στο Vercel!

Τι πρόκειται να οικοδομήσουμε;

Θα διασκεδάσουμε και θα δημιουργήσουμε μια εφαρμογή ιστού που χρησιμεύει ως βασικό wiki για τους χαρακτήρες Rick και Morty

Η εφαρμογή μας θα αποτελείται από μερικά πράγματα:

  • Μια λίστα χαρακτήρων στην πρώτη σελίδα
  • Ένα κουμπί που μπορεί να φορτώσει περισσότερους χαρακτήρες, καθώς το API είναι σελιδοποιημένο
  • Ένα πλαίσιο αναζήτησης για αναζήτηση χαρακτήρων
  • Μια σελίδα χαρακτήρων με βασικές λεπτομέρειες

Θα μάθουμε μερικές έννοιες όπως:

  • Πώς να δημιουργήσετε μια εφαρμογή ιστού με το Next.js
  • Τρόπος ανάκτησης και χρήσης δεδομένων από ένα API
  • Τρόπος προ-απόδοσης δεδομένων από ένα API
  • Πώς να ρυθμίσετε τη δυναμική δρομολόγηση

Τι είναι το Next.js;

Το Next.js είναι ένα πλαίσιο React από τη Vercel. Σας επιτρέπει να δημιουργήσετε εύκολα ελαφριές δυναμικές εφαρμογές ιστού με έναν τόνο σύγχρονων δυνατοτήτων που θα περίμενε κανείς εκτός συσκευασίας.

Η Vercel, η εταιρεία που υποστηρίζει το Next.js, είναι μια υπηρεσία που σας επιτρέπει να αυτοματοποιήσετε αγωγούς συνεχούς ανάπτυξης για να αναπτύξετε εύκολα εφαρμογές ιστού στον κόσμο. Θα χρησιμοποιήσουμε επίσης το εργαλείο γραμμής εντολών του Vercel για να αναπτύξουμε προαιρετικά το νέο μας demo wiki.

Βήμα 0: Ρύθμιση μιας νέας εφαρμογής Next.js

Για να ξεκινήσετε, ας ολοκληρώσουμε το έργο Next.js. Για να ξεκινήσουμε, θα χρησιμοποιήσουμε npm ή νήματα:

yarn create next-app # or npx create-next-app 

Μόλις εκτελέσετε αυτήν την εντολή, θα σας θέσει μερικές ερωτήσεις. Θα καλέσω το πρόγραμμά μου my-rick-and-morty-wiki, αλλά μπορείτε να το ονομάσετε ό, τι θέλετε.

Στη συνέχεια, θα σας ρωτήσει ποιο πρότυπο θα επιλέξετε - προχωρήστε και επιλέξτε το προεπιλεγμένο πρότυπο.

Τέλος, θα εγκαταστήσει όλες τις εξαρτήσεις.

Όταν τελειώσει, μπορείτε να πλοηγηθείτε σε αυτόν τον νέο κατάλογο και να εκτελέσετε:

yarn dev # or npm run dev 

Τώρα πρέπει να έχετε έναν τοπικό διακομιστή που εκτελείται στο // localhost: 3000!

Βήμα 1: Ανάκτηση χαρακτήρων Rick και Morty με ένα API στο Next.js

Τώρα που έχουμε ρυθμίσει την εφαρμογή μας, το πρώτο πράγμα που πρέπει να δημιουργήσουμε πραγματικά το wiki μας είναι μια λίστα χαρακτήρων.

Για να το κάνουμε αυτό, θα ξεκινήσουμε στην αρχική μας σελίδα στο pages/index.js.

Το Next.js σκαλίζει αυτόματα αυτήν τη σελίδα για εμάς. Είναι η πρώτη σελίδα που κάποιος θα χτυπήσει στον ιστότοπό μας και έχει κάποιες βασικές δυνατότητες στο προεπιλεγμένο πρότυπο όπως έναν τίτλο, ένα απλό πλέγμα και μερικά στυλ.

Προς το παρόν, αυτή η σελίδα δεν ζητά δεδομένα. Για να πάρουμε τους χαρακτήρες μας, θα πηδήσουμε αμέσως ζητώντας αυτήν την πλευρά του διακομιστή.

Για να γίνει αυτό, το Next.js μας επιτρέπει να εξάγουμε μια getServerSidePropsσυνάρτηση ασύγχρονου ακριβώς δίπλα στη σελίδα μας, την οποία θα χρησιμοποιήσει για να εισάγει τη σελίδα μας με οποιαδήποτε δεδομένα που συλλέγουμε.

Ας ξεκινήσουμε προσθέτοντας το ακόλουθο απόσπασμα πάνω από το Homeστοιχείο λειτουργίας μας :

const defaultEndpoint = `//rickandmortyapi.com/api/character/`; export async function getServerSideProps() { const res = await fetch(defaultEndpoint) const data = await res.json(); return { props: { data } } } 

Εδώ κάνουμε:

  • Ρυθμίζουμε μια μεταβλητή που ονομάζεται defaultEndpointαπλά ορίζει το προεπιλεγμένο τελικό σημείο API
  • Ορίζουμε τη getServerSidePropsλειτουργία μας την οποία θα χρησιμοποιήσουμε για τη λήψη των δεδομένων μας
  • Σε αυτήν τη συνάρτηση, χρησιμοποιούμε πρώτα το fetchAPI για να υποβάλουμε ένα αίτημα στο τελικό μας σημείο
  • Με την απόκρισή του, τρέχουμε τη jsonμέθοδο έτσι ώστε να πάρουμε την έξοδο σε μορφή JSON
  • Τέλος, επιστρέφουμε ένα αντικείμενο στο οποίο το κάνουμε dataδιαθέσιμο ως στήριγμα στην propsιδιοκτησία

Τώρα που υποβάλλουμε αυτό το αίτημα, πρέπει να το κάνουμε διαθέσιμο για χρήση.

Το dataδικό μας είναι διαθέσιμο ως στήριγμα, οπότε ας δημιουργήσουμε ένα επιχείρημα στη Homeσυνάρτηση συστατικών μας για να το κατανοήσουμε:

export default function Home({ data }) { 

Για να το δοκιμάσουμε, μπορούμε να το χρησιμοποιήσουμε console.logγια να δούμε τα αποτελέσματα:

export default function Home({ data }) { console.log('data', data); 

Και μόλις αποθηκεύσουμε και φορτώσουμε ξανά τη σελίδα, μπορούμε τώρα να δούμε τα αποτελέσματά μας!

Ακολουθήστε μαζί με τη δέσμευση!

Βήμα 2: Εμφάνιση χαρακτήρων Rick και Morty στη σελίδα

Τώρα που έχουμε τα δεδομένα χαρακτήρων, ας τα εμφανίσουμε στη σελίδα μας.

To start, I’m going to make a few tweaks. I’m going to update:

  • The

    title to “Wubba Lubba Dub Dub!”

  • The

    description to “Rick and Morty Character Wiki”

I’m also going to update the contents of

to:


     
  • My Character

What I’m doing here:

  • I’m making the a list as that will be better for accessibility
  • I’m making the
  • of the
      the card
    • And just changing the

      to “My Character” temporarily

    To make sure our new

      doesn’t mess up the layout with it’s default styles, let’s also add the following to the bottom of the .grid CSS rules:

      list-style: none; margin-left: 0; padding-left: 0; 

      And now if we look at the page, we should see our basic changes.

      Next, let’s make our grid load our characters.

      At the top of our Home component function, let’s add:

      const { results = [] } = data; 

      That will destructure our results array from our data object.

      Next, let’s update our grid code:

      
             
        {results.map(result => { const { id, name } = result; return (
      • { name }

      • ) })}

      Here’s what we’re doing:

      • We’re using the map method to create a new list element for each of our results (or characters)
      • Inside of that, we’re grabbing the id and name from each  character result
      • We’re using the ID as a key for our list element to make React happy
      • We’re updating our header with the name

      And once you save and reload the page, we should now see a new list of our characters from the API!

      We can also add an image for each character.

      First, inside of our grid, let’s update our destructure statement to grab the image URL:

      const { id, name, image } = result; 

      Next, let’s add the image above our header:

      And now each of our characters also shows their picture!

      Follow along with the commit!

      Step 3: Loading more Rick and Morty characters

      Now if you notice, when we load the page, we only get a certain number of results. By default, the API won’t return the entire list of characters, which makes sense, because it’s really long!

      Instead, it uses pagination and provides us with the “next” endpoint, or the next page of results, that will allow us to load in more results.

      To start, we’re going to use React’s useState hook to store our results in state. We’ll then have the ability to update that state with more results.

      First, let’s import useState from React:

      import { useState } from 'react'; 

      Next, let’s create our state by first renaming our original results variable and setting up our useState instance:

      const { results: defaultResults = [] } = data; const [results, updateResults] = useState(defaultResults); 

      If you save that and reload the page, you shouldn’t notice anything different yet.

      Next, we want to be able to understand in our application what our current endpoint we’ve made a request is, what the next endpoint is, what the previous endpoint is, and how we can update all of that.

      To do this, we’re going to create more state. First, we want to update our destructuring statement with our data to get the info value:

      const { info, results: defaultResults = [] } = data; 

      Next, let’s set up some state using that:

      const [page, updatePage] = useState({ ...info, current: defaultEndpoint }); 

      Here, we’re:

      • Creating a new page state that we can use to get our prev and next values
      • We’re also creating a new value called current we’ll we start off by using our defaultEndpoint, which was the request made on the server

      The idea here, is when we want to load more results, we’re going set up code to watch the value of current and update that value with the next, so when it  changes, we’ll make a new request.

      To do that, let’s add a useEffect hook to make that request:

      const { current } = page; useEffect(() => { if ( current === defaultEndpoint ) return; async function request() { const res = await fetch(current) const nextData = await res.json(); updatePage({ current, ...nextData.info }); if ( !nextData.info?.prev ) { updateResults(nextData.results); return; } updateResults(prev => { return [ ...prev, ...nextData.results ] }); } request(); }, [current]); 

      Here’s what’s going on:

      • First, we’re destructuring the current value from `page
      • We’re creating a useEffect hook that uses current as a dependency. If they value changes, the hook will run
      • If our current value is the same as defaultEndpoint, we don’t run the code, as we already have our request data. Prevents and extra on load request
      • We create an async function that we’re able to run. This allows us to use async/await inside of our useEffect hook
      • We make the request to the current endpoint. With that successful request, we update the page state with the new info like the new prev and next value
      • If our request does not have a previous value, that means it’s the first set of results for the given request, so we should completely replace our results to start from scratch
      • If we do have a previous value, concatenate our new results to the old, as that means we just requested the next page of results

      Again, if you save and reload the page, this still shouldn’t do anything and your page should be where it was before.

      Finally, we’re going to create a Load More button and use it to update the current value to fire off a new request when we want a new page.

      To do that, let’s first add a new button below our grid:

      Load More

      Now we want something to happen when we click that button, so first add an event handler:

      Load More 

      Then above the component return statement, let’s add that function:

      function handleLoadMore() { updatePage(prev => { return { ...prev, current: page?.next } }); } 

      When triggered with our button click, this function will update our page state with a new current value, specifically with the next value which is the endpoint to fetch our next page of results.

      And when we save and reload the page, it does just that!

      Follow along with the commit!

      Step 4: Adding the ability to search for Rick and Morty characters

      One of the features out Rick and Morty API provides is the ability to filter results — so basically the ability to search. So let’s add that as a feature.

      First, we need a search form. Let’s add the following snippet under the description paragraph:

        Search  

      Next, let’s add these styles to the bottom of the first block:

      .search input { margin-right: .5em; } @media (max-width: 600px) { .search input { margin-right: 0; margin-bottom: .5em; } .search input, .search button { width: 100%; } } 

      That’s going to give some spacing to our search input and button as well as make it mobile friendly. Feel free to add more styles if you’d like.

      And if we save and refresh our page, we have a simple form.

      It doesn’t do anything yet, so let’s make it search when submit the form.

      To start, let’s add an onSubmit attribute to our form:

      And to go with that, let’s define our submit function above our return statement:

      function handleOnSubmitSearch(e) { e.preventDefault(); const { currentTarget = {} } = e; const fields = Array.from(currentTarget?.elements); const fieldQuery = fields.find(field => field.name === 'query'); const value = fieldQuery.value || ''; const endpoint = `//rickandmortyapi.com/api/character/?name=${value}`; updatePage({ current: endpoint }); } 

      Here’s what we’re  doing:

      • First we’re preventing default behavior from the form submission to prevent the page from reloading
      • Next we grab the current target, which is our form
      • We grab the fields from the form by using the elements property. We also turn this into an array so it’s easy to work with
      • We search those fields for our query input
      • We grab the value of that input
      • We create a new endpoint where we filter by name using that query value
      • Finally, we update our current property in our page state to trigger a new request to that endpoint

      And once you save that and reload the page, you can now give search a try. You should be able to type in a name like “rick”, hit enter or click the search button, and you should now see filtered results with the various ricks across the universe!

      Follow along with the commit!

      Step 5: Using dynamic routes to link to Rick and Morty character pages

      Now that we have all of our characters, we want to be able to click into those characters and display some additional details. To do that, we’re  going to make use of Next.js’s dynamic routes.

      The first thing we need to do is properly configure our directory structure so Next.js recognizes the dynamic path. In order to set up a dynamic route, we need to create our folder exactly like:

      - pages -- character --- [id] -- index.js 

      Yes, that means you’re literally creating a folder with the name of [id], that’s not meant to be replaced. Next.js recognizes that pattern and will let us use that to create a dynamic route.

      To make creating the page easier, we’re going to simply duplicate our homepage by copying our pages/index.js file into our next directly.

      So we should now have a new page at pages/character/[id]/index.js.

      Next, let’s remove a bunch of stuff so we can get to a good starting point:

      • Remove everything above the return statement in our page’s function component
      • Rename the function component Character
      • Remove the useState and useEffect imports
      • Remove the description, search form, grid, and load more button
      • Optional: remove the footer

      Once you’re done, the top of our page’s function component should look like:

      export default function Character({ data }) { return ( Create Next App    

      Wubba Lubba Dub Dub!

      While there is some CSS we don’t need, we’re going to leave it all there for this demo. Feel free to clean some of that out later.

      If you navigate manually to /character/1, you should now see a simple page with just a title:

      Next, let’s update the data we’re fetching. We can reuse most of the code in our getServerSideProps function.

      We’re going to add a new argument to that getServerSideProps function:

      export async function getServerSideProps({ query }) { 

      When our page is rendered, Next.js injects data into our page and the getServerSideProps function about the environment. Here, we’re destructuring that data to grab the query object which will include any dynamic routing attributes, such as the [id] we’re setting in the route.

      Next, at the top of the getServerSideProps function, let’s destructure the ID:

      const { id } = query; 

      And finally let’s use that ID to dynamically create an endpoint we’ll use to fetch our character data:

      const res = await fetch(`${defaultEndpoint}${id}`); 

      Here, we’re using our character endpoint and appending the dynamic ID of our URL to the end of the URL.

      To test this out, let’s add a console.log to the top of the Character function:

      export default function Character({ data }) { console.log('data', data); 

      And if we hit save and reload our page, we should now see the user details about character number 1 logged out, which is Rick Sanchez!

      So we have the data, let’s add it to our page.

      At the top of the character function, let’s add this destructure statement:

      const { name, image, gender, location, origin, species, status } = data; 

      This gives us a bunch of attributes we’re getting right from that data object we saw logged out.

      To use that, we can start by updating the title to that name:

      { name } 

      Also the

      :

      { name }

      At this point, we should now dynamically see Rick’s name.

      Next, let’s add this block below our

      to include more of our character details:

      Character Details

      • Name: { name }
      • Status: { status }
      • Gender: { gender }
      • Species: { species }
      • Location: { location?.name }
      • Originally From: { origin?.name }

      Here we’re using our characters image to display a picture of our character and other various metadata to add Character Details.

      We can follow that up by adding this snippet of CSS to our styles:

      .profile { display: flex; margin-top: 2em; } @media (max-width: 600px) { .profile { flex-direction: column; } } .profile-image { margin-right: 2em; } @media (max-width: 600px) { .profile-image { max-width: 100%; margin: 0 auto; } } 

      And now we have our character bio!

      So a quick recap, we have our new dynamic page. We can go to /character/1 or any ID to see a specific character. Let’s now update our homepage to link to these pages.

      Back on pages/index.js, our homepage, let’s first import the Link component from Next.js:

      import Link from 'next/link' 

      Next, inside of our grid where we map through our list of results, let’s use our component and update our code:

    • { name }

    • Here’s what we’re doing:

      • First we’re wrapping our element with a component
      • We add a href and the as properties to describe to Next.js what page we want to link to. We need to use the as property as it’s a dynamic link
      • We remove the href from our element as it’s now being applied to the element

      If we save and reload our homepage, we’ll notice that nothing changed, but when we click any of our characters, we now go to their bio page!

      Finally, let’s also add a button to our character bio page that links back to our homepage to make it easier to navigate.

      First, let’s import the Link component:

      import Link from 'next/link'; 

      At the bottom of our tag below our .profile div, let’s add this code:

      Back to All Characters

      And we can add the following basic styles to simply make it look like a link:

      .back a { color: blue; text-decoration: underline; } 

      And if we reload the page, we now have link that we can click to go back to the main page with all of our characters!

      Follow along with the commit!

      Bonus Step: Deploy your Rick and Morty wiki to Vercel!

      Because we’re using Next.js, Vercel makes it super simple to deploy our app.

      To do this, we need to install the Vercel CLI. We can do that by installing it as an npm module globally:

      yarn global add vercel # or npm i -g vercel 

      Now, you  can run the vercel command in your terminal.

      The first time you run this, you’ll be prompted to log in. You’ll want to use your Vercel account to do this. If you don’t have one, you’ll want to sign up for a free account.

      With the Vercel CLI installed, we can simply run vercel in our project directory, fill out a few questions, and it will automatically deploy!

      You can use pretty much all of the defaults, though you will probably need to use a different project name than I’m using.

      But once finished, we now have successfully deployed our new app to Vercel!

      What else can we do?

      More dynamic pages

      Every time you make a request to a character, the API returns other endpoints that you can use such as locations and episodes. We can utilize those endpoints and create new dynamic pages, just like our dynamic character profile pages, to allow people to see more information about a specific location or episode.

      Add some styles

      We stuck with some of the basic styles that Next.js included and added some basic ones just for demonstration purposes. But now that you’re finished, you can have some fun and make it your own!

      Add character filters

      In addition to filtering by name, the API also supports filtering by status. By adding a status parameter to the endpoint URL, just like our name parameter, you can add a new filter to make it easier to find characters that are still alive or not.

      Follow me for more Javascript, UX, and other interesting things!

      • ? Follow Me On Twitter
      • ?️ Subscribe To My Youtube
      • ✉️ Sign Up For My Newsletter