Build out Articles page

In this article we're going to finish up and style our Article component. We'll show how we can render different markdown files for different urls in our app. We'll also look at what to do when a file is not found during a render.

We're going to start where we left off in the last article.
If you need to catch up:

git clone https://github.com/lawwantsin/webpack-course.git
cd webpack-course
git checkout articles
npm install

Data from the Future

So, as of now we're still pulling in our bio.md into the Article's page. Let's add some more markdown files to our data directory. I'm going to pull a couple from the final git repo.

In terminal:

git checkout articles-final data

That just pulled in 2 posts for Link and 2 for Zelda. So we can access them by going to http://link.local/article/post or http://link.local/article/post2.

As you can see, they're normal markdown files with the front-matter meta data at top. You can create whatever you want in this space. I'm naming them post and post2 for convenience and I'll show you how to automatically name them for your title a little later.

The important thing is that the folders we place it in inside data, match our site values.

Let's update Article.js to pull in a file based on it's slug param:

import React from "react"
import "../css/Article.css"
import NotFound from "./NotFound"

export default props => {
  try {
    const MarkdownData = require(`../../data/${props.site}/${
      props.match.params.slug
    }.md`)

    import(`../css/${props.site}/theme.css`)
    // require(`../css/${props.site}/theme.css`)

    const posterStyle = {
      backgroundImage: `url( ${MarkdownData.posterImage})`
    }

    return (
      <div className="Article">
        <div className="poster" style={style} />
        <h1>{MarkdownData.title}</h1>
        <div
          className="content"
          dangerouslySetInnerHTML={{ __html: MarkdownData.__content }}
        />
      </div>
    )
  } catch (error) {
    return <NotFound />
  }
}

Inside Article, let's move the MarkdownData require into the function and use the params object, which match gave us. Remember we said anything that came after article would be called slug. So here's slug. We can plug slug into the require and out pops real data we can use in the page.

We can use the require here in place of the import to allow us to edit the CSS in realtime, until Hot module reloading is supported with dynamic imports().

Not Found

In Routes.js:

But what if we type something into the browser that we don't have a markdown file for? Let's wrap it in a try block so if it throws an error, we can fail gracefully.

In the catch, let's return a new NotFound component. Let's create that now.

In terminal:

touch src/components/NotFound.js
touch src/css/NotFound.js

In components/NotFound.js:

import React from "react"
import "../css/NotFound.css"

export default ({ children }) => (
  <div className="NotFound">
    <div className="inner">
      <h1>Not Found</h1>
    </div>
  </div>
)

In css/NotFound.css:

.NotFound {
  display: flex;
  justify-content: center;
  -ms-align-items: center;
  align-items: center;
  height: 100vh;
}

.NotFound .inner {
  display: flex;
  -ms-align-items: center;
  align-items: center;
  justify-content: center;
  background-color: #fff;
  height: 300px;
  width: 300px;
  border-radius: 100%;
}

Finally, in Routes.js let's add this to the bottom of our Routes as a catch-all:

<Route component={NotFound} />

Okay so if we type a new url into the location bar. like http://link.local:8080/article/nope, we render the Not Found page instead.

Back to the Article component. Let's give the top poster image some quick styles, so we're really getting the full impact.

In css/Article.css:

.Article {
  margin: 200px auto;
}

.Article .poster {
  position: fixed;
  height: 500px;
  width: 100%;
  top: 30px;
  z-index: 0;
  background-color: #111;
  border-bottom: 4px solid white;
  background-position: center center;
}

.Article h1 {
  box-sizing: border-box;
  position: relative;
  z-index: 2;
  color: white;
  font-weight: 100;
  font-size: 4em;
  width: 820px;
  padding-left: 400px;
  padding-right: 40px;
  text-align: right;
  margin: 0 auto -13px auto;
  text-shadow: 0 0 20px black;
}

We need to change the content css to place this over-top the poster. In content.css:

.content {
  background-color: #ffffe0;
  box-shadow: 0 0 20px black;
  line-height: 2em;
  margin: 0 auto;
  padding: 40px;
  border-radius: 10px;
  z-index: 1;
  font-size: 1.2em;
  max-width: 740px;
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: 0 40px;
}

.content img {
  width: 100%;
  padding: 10px;
  margin-top: 7px;
  background-color: #fff;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);
}

@media (max-width: 768px) {
  .content {
    grid-gap: 0;
    grid-template-columns: 1fr;
    margin: 0 0 20px 0;
  }
}

As for Zelda, her poster is a little low. Let's change it's position with Zelda's theme.

In zelda/theme.css:

.Article .poster {
  background-position: 0 -100px;
}

Ok, this gives us a good handle on creating and editing posts in development. We can use our theming file if needed.

In Routes.js Let's create a few more items in the nav so we can:

<Link to="/article/post">Article</Link>
<Link to="/article/post2">Article 2</Link>

If you look closely, you will notice the nav bar is now hidden under the poster. Let's fix that.

In nav.css:

.nav {
  width: 100%;
  line-height: 2em;
  background-color: black;

  position: fixed; // Add these lines.
  z-index: 2;
  top: 0;
}

And, since we changed our content.css, we've broken our About.css. So let's separate them into jus the component styles and do away with content.css.

In About.css:

.profile .content {
  background-color: #ffffe0;
  box-shadow: 0 0 20px black;
  line-height: 2em;
  margin: 0 auto;
  padding: 40px;
  border-radius: 10px;
  z-index: 1;
  font-size: 1.2em;
  max-width: 740px;
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
}

I like that the files are still hot editable. So you can see what you'll get when you write.

In Sum

Okay, so now we've got a real handle on the development side of this blog. I'm aware that, although this technically works, it wouldn't be useful in production. We've got markdown that will grow with each new post. We'll need to separate the markdown from the bundles in future episodes.

Up Next

Before we move on to the production data-side. The last thing we need is security. Next, we'll be adding SSL to our new domains by creating both a custom authority and a self-sign certificate.