DEV Community

Cover image for Introduction to the basics of Next.js
aurel kurtula
aurel kurtula

Posted on • Updated on

Introduction to the basics of Next.js

Next.js is a framework that renders react applications on the server. This means when we create a normal react app (using create-react-app for example) and load it to the browser, all the content is created by client-side javascript. Hence users or devices that don't have javascript will not be able to access your app.

You can see this in action, open a react application in the browser then view the page source. You will not see the content you've created! In fact, if the project was created through create-react-app you'll see this:

<noscript>
  You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
Enter fullscreen mode Exit fullscreen mode

Which is obvious as all we're doing with react is appending content to #root(hence document.getElementById('root')). Content that vanishes if javascript is disabled.

Hence Next.js, it fixes that issue for us.

Whilst learning next.js I am trying to create a small application that will have the basic functionality of instagram - a mini instagram clone if you like. I know that the code we'll write today will not be scalable, hence will not contribute that much to the react application but it will cover the basics of next.js

Let's get started

First we need to initialise npm and install the only packages needed for next.js to work:

npm init
npm install --save next react react-dom
Enter fullscreen mode Exit fullscreen mode

(If you use yarn you know what to do)

Now we need to add the following scripts to the generated package.json:

  "scripts": {
    "start": "next"
  },
Enter fullscreen mode Exit fullscreen mode

When we want to run this app in the browser we'll need to run npm start.

Next.js allows us to organise our routes via pages. In this tutorial we'll create two pages to see how they can interact. Let's first create the index page. It needs to be at ./pages/index.js. In there add the following:

export default () => (
    <div className="app">
        <header>
            <h1>gallery</h1><h2>Original art</h2>
        </header>
    </div>
)
Enter fullscreen mode Exit fullscreen mode

And that's it.

If we run npm start we'll have the index page displayed. And of course, the most important thing here, if you view the page source, you'll now see the above HTML "hard coded" for us by next js.

Styling

CSS styles can go right into the pages/index.js. Making each section modular. Let's make that header pretty:


export default () => (
    <div className="app">
        <header>
          <h1><Link href={{ pathname: '/' }}><a>gallery</a></Link></h1>
        </header>
        <style jsx>{`
            @import url('https://fonts.googleapis.com/css?family=Oleo+Script');
            //... you can get the full code at github
            header h1{
                font-family: 'Oleo Script', cursive;
                font-size: 25rem;
                color: #AD0044;
                text-shadow: #7F0D23 0px 0 40px;
                letter-spacing: -20px;

                display: inline-block;
            }
            h2{
                font-size: 3.5rem;
                color: white;
                text-shadow: none;
                letter-spacing: normal;
                font-weight: normal;
                font-family: 'Dancing Script', cursive;
                position: absolute;
                top: 50%;
                left: 50%;
            }

        `}</style>
    </div>
)

Enter fullscreen mode Exit fullscreen mode

From the documentation "We bundle styled-jsx to provide support for isolated scoped CSS"

Adding styles this way feels like a hack! I like the external styles better, but it really does help with keeping everything isolated. I just need to get used to it - maybe.

Adding components

Let's just further split this code up so that it does not get messy by adding a layout component in ./components/Layout.js

import Link from 'next/link'
import Head from 'next/head'

export default ({ children, title = 'gallery: original art' }) => (
    <div>
        <Head>
        <title>{ title }</title>
        <meta charSet='utf-8' />
        <meta name='viewport' content='initial-scale=1.0, width=device-width' />
        </Head>
        <div className="app">
            <header>
                <h1><Link href={{ pathname: '/' }}>gallery</Link></h1>
                <h2>Original art</h2> 
            </header>
            { children }
            <footer>
            Footer
            </footer>
        </div>
        <style global jsx>{`
            :root {
                --green:  #65C5D9; 
                --white: #F4F5F7;
                --light-gray: #EAEEEF;
            }
            ...
            body{...}
        `}</style>
        <style jsx>{`
            header{ ...}
            header h1 a{ ...}
            h2{....}
            footer { ...}

        `}</style>
    </div>
)
Enter fullscreen mode Exit fullscreen mode

Few important things to note above:

  1. Next.js gives us a Head component where by we can manipulate what goes inside the HTML <head> tag. For example you might want more meta information.
  2. We should use the Link component to navigate through the pages. It does require an anchor tag as well. I added an empty anchor tag but Next does populate it accordingly.
  3. I used two style tags, one being global. The perfect example are the css variables and the body tag. Each page/component needs access to that information, hence global.

Back in the ./pages/index.js we proceed as such:

import Layout from '../components/Layout';
export default () => (
    <Layout>
       <p>this is the content </p>
    </Layout>
)
Enter fullscreen mode Exit fullscreen mode

Working with some data.

We could convert the Index page component to a react component and get the data in the react component lifecycle or keep going with the stateless function approach. For this tutorial we're going to keep it simple and will not introduce react components (we'll do that in the next tutorial)

import Layout from '../components/Layout';
import getPhotos from '../data/data.js'
const Index = (props) => (
    <Layout>
       <p>{props.images[0].tagline}</p>
    </Layout>
)
Index.getInitialProps = async ({ }) => {
  // Would fetch data
  return {  images: getPhotos()  } // return { images: [ { }, { } ] }
}

export default Index
Enter fullscreen mode Exit fullscreen mode

getInitialProps we would perform fetch operations and pass the fetched data to the Index props, but we are hard coding it here just for convenience. We have the hard coded data at ./data/data.js which looks like this:

export default () => {
    return [
        {
            id: 0,
            tagline: 'You\'re looking at me punk?', 
            image: '927756_283684128492129_838664181_n',
            likes: 2,
            comments: [
                {
                    user: 'rex2018',
                    body: 'Hey this is dope! xxx'
                }
            ]
        },
        ...
    ]
  }
Enter fullscreen mode Exit fullscreen mode

All that data is available through the props. How we play with that data inside the Index component is basic react stuff. First lets edit the Index component

import Layout from '../components/Layout';
import getPhotos from '../data/data.js'
import Photo from '../components/Photo';
const Index = (props) => (
    <Layout>
       {
        props.images.map((image, key) => <Photo id={key} id={key} data={image} />)
       }
    </Layout>
)
...
Enter fullscreen mode Exit fullscreen mode

We loop through each image and render a Photo component. We need to create that component at ./components/Photo.js.

import CommentsFunctionality from './InteractiveButtons'
import Link from 'next/link'

export default (props) => {
    return (
        <div className="photoComponent">
            <div style={{flex: '1 0 auto'}}>
                <Link href={{ pathname: '/photo', query: { id: props.id } }}>
                    <img src={`/static/art/${props.data.image}.jpg`} alt=""/>
                </Link>
                <div className="meta">
                    <p className="tagline">{props.data.tagline}</p>
                    <CommentsFunctionality likes={props.data.likes} />
                </div>
            </div>
            <style>{`
                .photoComponent {
                    ...
                }
            `}</style>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

To recap, in ./pages/Index.js we loop through all the images. For each image we render the above Photo component. The new thing in there is that I decided to move a little bit of code into a separate InteractiveButtons component. Initially I did so purely to keep the CSS of Photo component clean. Anyway, heres the code for InteractiveButtons

import { MdModeComment, MdFavoriteBorder } from 'react-icons/md'
export default ({likes}) => (
    <div className="meta">
        <a href="/" className="heart"><MdFavoriteBorder />34</a>
        <a href="/"><MdModeComment />{likes}</a>
        <style>{`....`}</style>
    </div>
)
Enter fullscreen mode Exit fullscreen mode

I just found the react-icons npm module whilst looking for a way to add icons without downloading any css framework, and I really like this. It's so easy to use. You can see all the icons provided here. Above I am requesting MdModeComment and MdFavoriteBorder from the Material Design icons (hence /md)

Static Files

In the ./components/Photo.js component we are trying to access the image file from a static directory. All we need to do is to create that directory and add the image file, and Next.js takes care of the rest.

The end result so far:

Creating another page

We now need to be able to click on one of the above photos and see more information about them.

In the ./components/Photo.js component we had this code

<Link href={{ pathname: '/photo', query: { id: props.id } }}>
    <img src={`/static/art/${props.data.image}.jpg`} alt=""/>
</Link>
Enter fullscreen mode Exit fullscreen mode

Which means, on click we go to /photo?id=0. So the Link component accepts a query where we can add our own query strings. In this case we simply get the id from the props and pass it on.

We then need to create ./pages/photo.js page.

import Layout from '../components/Layout';
import Photo from '../components/Photo';
import CommentsFunctionality from '../components/InteractiveButtons'
import getPhotos from '../data/data.js' 
const PhotoPage = (props) => (
    <Layout>
        <div className="container">
            <div className="display_image">
                <img src={`/static/art/${props.image.image}.jpg`} alt=''/>
                <CommentsFunctionality />
            </div>
            <div className="comments">
                <p className="tagline">{props.image.tagline}</p>
                {
                    props.image.comments.map((comment, key) => <p key={key}><strong>{comment.user}:</strong>{comment.body}</p>)
                }
                <form className="comment-form" >
                    <input type="text"placeholder="Author" />
                    <input type="text"  placeholder="comment..." />
                    <input type="submit" hidden />
                </form>
            </div>
        </div>
        <style>{` `}</style>
    </Layout>
)

PhotoPage.getInitialProps = async ({query}) => {
    // could fetch data here
    let {id} = {...query}
    let image = getPhotos().find(m => m.id == id)
    return { image } 
}

export default PhotoPage
Enter fullscreen mode Exit fullscreen mode

The important functionality is inside getInitialProps. Amongst other information getInitialProps has access to the query string, hence we pass the as {query}, from which we get the id that comes from the index page. Then we just find the image object that has the same id and pass it as the component props.

That's the only reason I added the data in a separate file (at ./data/data.js). The most basic way of showing how getInitialProps could fetch data based on the query strings. The fetch url would look something like this fetch("http:.../" + id)

The single photo page is done:

Conclusion

As it can easily be seen this was not the best approach when it comes to handling data. We would really need to add react components and make use of react's component life cycle. Though I would argue even that would fall short compared to redux.

In the next tutorial, as I said, we'll use react components, fetch the exact same data from an API and get the likes and comments more dynamicly.

Meanwhile you can get all this code in github (branch 'part1')

Oldest comments (3)

Collapse
 
tsterrm profile image
TsterRM

Would you happen to have a video tutorial of this? Because I got lost in the reading. Plus, I'm unable to run your project files from Github :/

Collapse
 
aurelkurtula profile image
aurel kurtula

Hi.

I don't have it on video, unfortunately.

I was about to just clone the repo, checkout part1 and npm install and then npm start and it just ran.

Collapse
 
imcsweeney profile image
imcsweeney

if anyone has a problem installing this having cloned from GitHub, see the 4th answer here: stackoverflow.com/questions/470081...

ie downgrade your version of npm to 10.15, to install all the dependencies (this worked on macOS Catalina)

curl -o- raw.githubusercontent.com/creation... | bash
sudo npm install -g n
sudo n 10.15
npm install
npm audit fix
npm start