Scoping per Domain

In this article we're going to look at how we can use our new multi-domain setup to load different data on a per domain basis. We'll look at the About page, and extend that into the Article and Gallery pages in future lessons.

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 domain-scope
npm install

So, we just setup a new top level domain to replace localhost. But the app doesn't recognize the different domains just yet. Let's add that using React router's concept of a static Context.

Scoping on Hostname

Inside render.js we have the base of our server side rendering and the file that holds the StaticRouter. Remember we talked about the context object that is required. We left it empty. Let's put a simple piece of information inside.

To do that, let's set the debugger in render.js:

In the Node Debugger Console. What are we looking for? It pauses. Okay, so we're going to want the hostname on the request. Look at the source code. Not our original code. I think dev-server needs some sourcemap love.

In webpack.dev-server.js:

devtool: "sourcemap",
const site = req.hostname.split(".")[0]
const context = { site }

res.send(`
<StaticRouter location={req.url} context={context}>
`)

We're using the host property on the request, which does not include the port number. We'll split that buy the period, until we're left with just the part of the domain we want to scope on.

In our case, link or zelda. We make context look like { site: 'link' } and we pass it to our Static Router as the context. This is known as the staticContext.

Now staticContext is only available during the Server-side render. It's not available in the client render, but both client and server use the Routes.js file.

We're going to want to

Inside our Routes.js:

<Switch>
  <Route exact path="/">
    <UniversalComponent page="Gallery" />
  </Route>
  <Route
    path="/about"
    render={({ staticContext }) => {
      const site = staticContext
        ? staticContext.site
        : location.hostname.split(".")[0]
      return <UniversalComponent site={site} page="About" />
    }}
  />
  <Route path="/article">
    <UniversalComponent page="Article" />
  </Route>
</Switch>

We're using the render prop on our Route component. This takes a function that will receive the static context from our StaticRouter.

This function serves to smooth out the difference between client and server. While server has a staticContext with our site data from the request, client does not have one. So it will pull the same info from location.hostname, which is assigned to site.

We want to place it into the UniversalComponent as a prop, so we can use it. Inside About.js let's use this new prop to require files more dynamically.

Let's bring the requires down into the render function and use props.site inside the strings to point to a dynamic folder.

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

export default props => {
  const siteConfig = require(`../../data/${props.site}/siteConfig.js`)
  const imagePath = require(`../images/${siteConfig.aboutImage}`)

  const MarkdownData = require(`../../data/${props.site}/bio.md`)

  return (
    <div>
      <div className="profile">
        <img src={imagePath} />
        <h1>{MarkdownData.title}</h1>
        <div
          className="content"
          dangerouslySetInnerHTML={{ __html: MarkdownData.__content }}
        />
      </div>
    </div>
  )
}

Alright, looks good. We see in the browser we should have an error. The way you know it's working is if we change the site domain to link.local, you see the error changes.

Error

We're looking for a file that doesn't exist. We can fix that.

Separate Assets for Different Domains

The new site needs a new image to set it apart. Let's bring in another image to be Zelda's about picture.

Because this one is .png, let's change our image loader to handle all kinds of image files.

In all 4 webpack config files:

  test: /\.(jpg|png|gif)$/,  // Change this
  use: [
    {
      loader: "file-loader",
    ...
  }]

Now we're going to want to grab any image, I'll use this one. In terminal

cd src/images
wget https://raw.githubusercontent.com/lawwantsin/webpack-course/domain-scope-final/src/images/zelda.png
cd ../..

Image of Zelda

Okay, now let's create some new folders. In terminal:

cd data
mkdir link zelda
cp bio.js link/siteConfig.js
mv bio.js zelda/siteConfig.js
cp post.md link/bio.md
mv post.md zelda/bio.md

In data/link/siteConfig.js:

module.exports = {
  aboutImage: "link.jpg"
}

In data/zelda/siteConfig.js:

module.exports = {
  aboutImage: "zelda.png"
}

So now we have a place to put site wide configuration data, like title text and images. As well as a use for our first markdown file. A basis for an about page bio.

Let's fill Zelda's in with different info, just for completeness sakes:

In data/zelda/bio.md:

---
title: About Zelda
author: Zelda
---

# How did you get past the guards?

Zelda wakes in a dark dungeon. Where is Link?

Let's spins these 2 sites up and see where we're at.

In the terminal:

npm run dev

In the browser let's go to http://link.local:8080/about and http://zelda.local:8080/about.

In data/link/bio.md let's make a change and see if it updates, let's update the title field:

title: About Link

HMR reloads the whole page. But it still works across domains.

In Sum

Side by side, we have 2 sites running on one codebase.

Finished

If you need the final code.

git checkout domain-scope-final

Up Next

We're not nearly done yet. We've got data coming in for both sub-sites, but we're still missing a bunch of features. Like theming with separate CSS files and of course, we'll need to split out these bundles for different domains, so this idea can scale. We'll look at all of this in this section.