Henry Unite

Building My Portfolio Website With Nextjs

It’s time for a new portfolio website! This time I decided to use Next.js to generate my static site with these principles in mind:

  1. Take a README markdown file of my resume and convert it to a static homepage
  2. Use semantic HTML with global styles for easy customization
  3. Adding next pages will append links to the homepage

README Conversion

The core concepts of this project are built on the foundation of these methods:

  1. Bootstrap a create-next-app
  2. Use the getStaticProps to generate HTML from the README with showdown
  3. Use dangerouslySetInnerHTML for SEO optimization

Getting Started with Next

We can start bootstrapping our application using the create-next-app npm script.

$ npx create-next-app

Generating HTML from README

Using getStaticProps and showdown, we can generate some HTML to use for our site generation.

export async function getStaticProps() {
  const path = require('path');
  const fs = require('fs');
  const { Converter } = require('showdown');
  const converter = new Converter();

  function parseREADME() {
    return new Promise((res) => {
      fs.readFile(path.join(process.cwd(), 'README.md'), (err, data) => {
        const readme = data.toString();
        const html = converter.makeHtml(pReadme);
        res(html);
      });
    });
  }

  const html = await parseREADME();

  return {
    props: { html },
  };
}

Serving HTML optimized for SEO

The key to using dangerouslySetInnerHTML with next.js is that we want to ensure the content of our HTML is served as static content for SEO.

return (
    <div>
      <Head>
        <title> {title} </title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main dangerouslySetInnerHTML={{ __html: html }} />

      <footer> 

      </footer>
    </div>
  );

Semantic Styling

After your content is being injected in the page, you should be staring at a wall of black and white text like this:

“Unstyled README Homepage”

Using the global.css file provided by next, we can globally style semantic elements like this:

body {
     /* CSS Styles */
}

main {
     /* CSS Styles */
}

main hr {
     /* CSS Styles */
}

main strong {
     /* CSS Styles */
}

main p {
     /* CSS Styles */
}

main h1, main h2, main h3, main h4, main h5, main h6 {
     /* CSS Styles */
}

main ul, main ol {
     /* CSS Styles */
}

main li {
     /* CSS Styles */
}

main a {
     /* CSS Styles */
}

Page Extensibility

One of the design concepts I wanted to implement was the idea that you could add a next page in the pages directory and a navigation link be appended to the homepage.

Taking advantage of the the getStaticProps function, we can use node to read the directory, exclude unrelated files, and generate links in our homepage.

// CONFIG['pageExcludes'] = [ 'app', 'api', 'index']

  function getPages() {
    return new Promise((res) => {
      fs.readdir(path.join(process.cwd(), 'pages'), (err, data) => {
        const pageFiles = data.filter((f) => {
          return !CONFIG['pageExcludes'].filter((ex) => f.includes(ex)).pop();
        });

        res(pageFiles.map((p) => p.replace('.js', '')));
      });
    });
  }

  const html = await parseREADME();
  const pages = await getPages();

  return {
    props: { html, pages },
  };
      <footer> 
        <div id="pages">
          { pages.map((p) => p ? <a key={p} href={`/${p}`}>{ p }</a> : null }
        </div>
      </footer>

Gathering my Blog Post Data

With this feature, I can now create unique CMS pages to extend my static site. Let’s create a blog page to fetch my DEV posts.

I’ll be using the axios library to make a request to the DEV api, gather my posts data, and send those props to the page for static site generation. Again, taking advantage of the getStaticProps hook.

// pages/blog.js

export async function getStaticProps() {
  const axios = require('axios');

  function getArticle() {
    return new Promise(async (res) => {
      const req = await axios({
        method: 'GET',
        url: 'https://dev.to/api/articles?username=unitehenry'
      });

      if(req['data']) {
        try {
          const data = req['data'];
          res(data.map((article) => {
            return {
              title: article['title'], 
              description: article['description'], 
              url: article['url'],
              date: article['created_at'],
              image: article['cover_image']
            };
          })); 
        } catch(e) {
          res([]);
        }
      } else {
        res([]);
      }
    }); 

  }

  const articles = await getArticle();

  return {
    props: { articles }
  }
}
        <section>

        { (articles.length === 0) && <p>No Blog Posts</p>}

        {
          articles.map(({ title, description, date, url, image }) => {
            return (
              <article key={title} className={style['blog-article']}>
                { image ? <img src={image} /> : null}
                <div className={style['blog-article-content']}>
                  <h2>{ title }</h2>
                  <p>{ description }</p>
                  <a title="Read Article" className={style['blog-button']} href={url} target="_blank">Read Article</a>
                </div>
              </article>
            );
          })
        }

        </section>

“Portfolio Footer Pages”

Bootstrapping of my Repository

If you want to see the source code or fork this repo and generate your own static site, I’ve created a GitHub repository and documented in detail how to customize the code for your own static portfolio site.

GitHub Trick

As a side note, there is a GitHub trick that will take your README and display it on your GitHub profile as well.

“GitHub Profile”