Vuepress Within a Laravel Application

Dan Pastori avatar
Dan Pastori August 30th, 2018

Let me kick this tutorial off right away, this is just an example of how I accomplished this. I feel that there could be multiple different ways, which I’d love to hear about. With that being said I did get Vuepress working within a Laravel application under a route. All Single Page Application features work as well such as navigation and loading of resources, but there are a few configurations I feel could be done differently. From the end user perspective though it is awesome and having the power of Vuepress within the application itself? Also amazing!!

Overview

For this tutorial we will be looking at 521dimensions.com and our open source project AmplitudeJS whose docs are now written in VuePress and are located at https://521dimensions.com/open-source/amplitudejs/docs.

Our 521dimensions.com site is written in Laravel 5.6 and all of the docs for AmplitudeJS are in VuePress. It’s nice to have the docs on the same domain, and look like just another route in the site.

How I Structured The Set Up

This is the first part that I felt I could be different. If you have any alternatives or suggestions, definitely let me know! So I made 2 directories. We have our /521dimensions.com directory and I made an /amplitude-docs directory outside of the actual site.

The reason I did this was because I use Laravel-Mix for compiling 521dimensions.com assets. Laravel Mix is amazing, however, it’s on web pack 3. If you read the VuePress guide on installation (Getting Started | VuePress), you can not install Vuepress as a local dependency with web pack 3. NOT A VUEPRESS PROBLEM, but a web pack problem. Web pack will generate a wrong dependency tree and everything will go to waste. They recommend using Yarn, but installing Yarn and Using NPM looks like a massive headache and I’ve read you can’t even make them work together.

The other option is to do a global install of vuepress. Thought about it, but I like keeping things local whenever possible. That’s why I made another directory called /amplitude-docs.

Installing VuePress

I’m not going to go into the details of installing Vuepress since the guide: Getting Started | VuePress handles it beautifully. What I did was create a new NPM project in the /amplitude-docs directory. I then followed the guide on getting started with VuePress and ran:

npm install -D vuepress

That installed VuePress as a local dependency in the project. Next I created an /amplitude-docs/docs directory. I then put a README.MD file in that directory and created the hidden .vuepress directory.

In the /docs directory you can add folders that contain documentation in Markdown for your app. In the VuePress config, you can set how you want these to be structured on the sidebar, but that’s dependent upon the theme you are using. Note that the equivalent to an index.html file is the README.md file. You will be adding one of these to each of your directories.

Configuring VuePress To Work With Laravel

Now, in the hidden .vuepress directory I added a config.js file. For AmplitudeJS docs I am simply using the default theme provided by VuePress. I love the theme and customized it to fit what I needed. You can check out the live docs here: Overview – AmplitudeJS Documentation. If you want to know how I configured or did something with VuePress to make the docs act a certain way, I’d be glad to help, just comment below! However, any configuration beyond what I did to make VuePress work with Laravel, is outside of the scope of this tutorial.

With that being said, open up your .vuepress/config.js file. There is an option in VuePress called base. This is EXTREMELY important to make this work as a sub-directory in Laravel. Before we set this, be aware of two commands:

  1. npm run docs:dev
  2. npm run docs:build

These two commands are set up when you install VuePress. The first command npm run docs:dev fires up a local development url so you can see your docs as you add them. npm run docs:build actually compiles all of your documentation into a rendered app that you can deploy to a variety of places: Deploying | VuePress. In our case, we will be deploying it to our server.

Back to the base configuration. If you look at the URL for our AmplitudeJS docs at https://521dimensions.com/open-source/amplitudejs/docs, notice how it’s in the /open-source/amplitudejs/docs directory? This will be set as your base! Your config should look like this for VuePress:

module.exports = {
  title: '',
  description: 'Official documentation for AmplitudeJS',
  base: '/open-source/amplitudejs/docs/',
}

This is where another configuration step lies that I don’t like. When building your docs and testing them, you should REMOVE the base configuration or it won’t work because it won’t match the directory structure of your VuePress application. When I’m testing my docs, I remove the base, let VuePress fire up a development URL at https://localhost:8080 or whatever you have configured, then before I build, I add my base config. Maybe you could remove this dependent on a node command? That could be another option. Anyway, to sum up, your base config should match the subdirectory your docs will lie in your route within your Laravel application.

Right now, go ahead and build your docs! Throw some Markdown in your directories, test it out and get it to a point where you want to deploy. Of course if you are at this point and have questions about a specific VuePress config I have set up, reach out below! Next, we will be moving the compiled docs to our Laravel Application.

Building our Docs For Deployment

Once you have your docs built, it’s ready for deployment! To build your docs within Vuepress, make sure your base is set to match the subdirectory or sub route they will end up in within your Laravel application. For our case this is /open-source/amplitudejs/docs. Then run npm run docs:build. Your documentation will be compiled into a single page application that’s rendered server side. Essentially this is a bunch of HTML, CSS, Javascript, and images used by your docs.

When npm run docs:build has completed, the docs will be compiled and placed in the /docs/.vuepress/dist directory. Essentially this is what you want to copy to your Laravel application! These are your production ready docs, they contain everything you need.

Now heres, the another step. You will need to place these docs in a public facing directory on your Laravel application. Right now, I’m assuming your docs do not contain sensitive information. IF THEY DO, do NOT place them in a public directory. Reach out, and I can help block off your docs through an authorization policy.

Right now, assume all docs are public. I will be placing my docs in a /public/docs/amplitudejs directory within my Laravel installation. To make this easier on me for my development environment, within VuePress I wrote a simple node script that was npm run docs:deploy. What that does is literally copy the compiled docs directory /docs/.vuepress/dist to the /public/docs/amplitudejs directory within my Laravel install. You can copy this however you want.

Now that our docs are inside of our Laravel installation, we have to do a few tricks to make them work!

Configuring Laravel to Work with VuePress

This the trick. We want our docs to reside at https://521dimensions.com/open-source/amplitudejs/docs. This is within our Laravel installation and under the route we defined. As I mentioned before, when we built our docs for production, we set our base as /open-source/amplitudejs/docs. This sets up all of the VuePress configuration and router to assume that the docs are at that directory. This is nice so all links actually stay consistent with the URL even within the single page application! Imagine that! Your Google Analytics will work as well if you configured those.

So in the last step we copied all of the production ready docs into a public directory at /public/docs/amplitudejs within the Laravel installation.

Our first step is to add the actual routes in Laravel that we want to load the docs with. Just a heads up, I have an AmplitudeController.php file that handles all routes pertaining to our AmplitudeJS section of the site such as landing page, examples, etc. We are using Laravel 5.6, so we need to open the /routes/web.php file and add the following routes:

Route::get('/open-source/amplitudejs/docs/', 'AmplitudeController@getAmplitudeDocsAsset');
Route::any('/open-source/amplitudejs/docs/{asset}', 'AmplitudeController@getAmplitudeDocsAsset')->where('asset', '.*');

There is a whole bunch going on within these two routes. The first one, is essentially a placeholder for saying hey, we are going to return docs at this address. The second one is a wild card and is really one of the major keys to making the VuePress docs work within laravel. Remember that base we set up? WELL, here’s where it comes to play. All resources are accessed from VuePress to that base URL. What the second route does is assigns ANYTHING after the /docs part of the URL to an $asset variable. It can literally be anything. This catches ALL requests back to load resources which we will be intercepting and returning from our public directory. We are essentially proxying ALL requests and loading the associated file from the public directory and returning it.

Notice the two routes access the same method on the AmplitudeController.php? That’s not by error. We only need one method to do all the things!

Let’s look at that method:

public function getAmplitudeDocsAsset( $asset = 'index.html' ){

  /*
    Disable new relic auto run so we don't get extra JS that will
    break the VuePress SPA
  */
  if (extension_loaded('newrelic')) {
    newrelic_disable_autorum();
  }

  /*
    If the extension is javascript, css, svg or does NOT
    contain .html redirect to the page they requested and the
    index.html file within that directory.
  */
  if( pathinfo( $asset, PATHINFO_EXTENSION ) != 'js'
      && pathinfo( $asset, PATHINFO_EXTENSION ) != 'css'
      && pathinfo( $asset, PATHINFO_EXTENSION ) != 'svg'
      && strpos( $asset, '.html' ) == null ){
        return redirect( '/open-source/amplitudejs/docs/'.$asset.'/index.html' );
  }

  /*
    Get the contents of the asset. This can be CSS, JS, images or HTML.
  */
  $contents = File::get(public_path() . '/docs/amplitudejs/'.$asset );

  /*
    Build a response with the asset being requested.
  */
  $response = Response::make($contents);

  /*
    If the asset is CSS, ensure the Content-Type text/css header is passed
    along with the file.
  */
  switch( pathinfo($asset, PATHINFO_EXTENSION) ){
    case 'css':
        $response->header('Content-Type', 'text/css');
    break;
  }

  /*
    Return the response
  */
  return $response;
}

There’s a LOT going on in this method, so let’s break it down. Essentially this is the key to making EVERYTHING work.

First, we disable NewRelic (if you are using NewRelic for monitoring or whatever):

/*
  Disable new relic auto run so we don't get extra JS that will
  break the VuePress SPA
*/
if (extension_loaded('newrelic')) {
  newrelic_disable_autorum();
}

We need to disable this because they throw extra javascript into our application. This breaks the VuePress SPA big time! We don’t want this at this point.

Next, we do a check on what URL the user accessed:

/*
  If the extension is javascript, css, svg or does NOT
  contain .html redirect to the page they requested and the
  index.html file within that directory.
*/
if( pathinfo( $asset, PATHINFO_EXTENSION ) != 'js'
    && pathinfo( $asset, PATHINFO_EXTENSION ) != 'css'
    && pathinfo( $asset, PATHINFO_EXTENSION ) != 'svg'
    && strpos( $asset, '.html' ) == null ){
      return redirect( '/open-source/amplitudejs/docs/'.$asset.'/index.html' );
}

If the URL is not Javascript, CSS, SVG and does not contain .html, we assume the user wants to visit a piece of the documentation. This is part of the trick. Remember, we are proxying all assets to be loaded through this route, so the page could be requesting Javascript, CSS, or image resources. We don’t want this redirect if they are requesting any of those. Now we have this in here to redirect to the index.html file for the appropriate sub directory. This is so VuePress handles the kicking off of the SPA correctly. Not a huge fan of this part, but it’s the way it needs to work so it doesn’t through a 404 when you try to navigate.

The next part of the method is pretty slick:

/*
  Get the contents of the asset. This can be CSS, JS, images or HTML.
*/
$contents = File::get(public_path() . '/docs/amplitudejs/'.$asset );

/*
  Build a response with the asset being requested.
*/
$response = Response::make($contents);

/*
  If the asset is CSS, ensure the Content-Type text/css header is passed
  along with the file.
*/
switch( pathinfo($asset, PATHINFO_EXTENSION) ){
  case 'css':
      $response->header('Content-Type', 'text/css');
  break;
}

/*
  Return the response
*/
return $response;

What this does is get the asset being requested from the public path. We have the $asset variable containing essentially the URL relative to the docs that VuePress generated. This is the same structure that is in our public directory so we can grab the file itself using the public_path() helper in Laravel, appending the source of where we placed the file, and grabbing the file based on the $asset parameter.

We then make a response using the Response facade that comes with Laravel and pass it the contents of the file we just loaded.

The only gotcha before we return the response is Laravel won’t add the proper header for Content-Type if the file is a CSS file. This becomes an issue because the browser will interpret it as text and won’t render the CSS making for an ugly page. So what we do is check to see if the extension on the $asset variable is css. If it is, we throw a quick header out there for the Content-Type: text/css so it will be interpreted correctly.

Finally, we return the $response with the file. This is all we need to do to get VuePress working within Laravel! Essentially we set a base of the VuePress docs to where we will be referencing it through a Laravel route. We then move the files to a public directory, and write a proxy for any requests from the VuePress SPA to be handled by our proxy for all images, scripts, styles, html, etc.

Conclusion

It is kind of a round about way to get this to work. Once I got it working, I am extremely happy with the results. You can see the live AmplitudeJS docs written in VuePress through our Laravel Application here: Overview – AmplitudeJS Documentation. Let me know your thoughts! Of course if there are questions, concerns, need more information or have a better way to do something, please reach out! There’s comments below! I have a lot of faith in VuePress for the future of docs and making this process more efficient would be awesome!

Support future content

The Ultimate Guide to Building APIs and Single-Page Applications with Laravel + VueJS + Capacitor book cover.

Psst... any earnings that we make off of our book is being reinvested to bringing you more content. If you like what you read, consider getting our book or get sweet perks by becoming a sponsor.

Written By Dan

Dan Pastori avatar Dan Pastori

Builder, creator, and maker. Dan Pastori is a Laravel certified developer with over 10 years experience in full stack development. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks.

Like this? Subscribe

We're privacy advocates. We will never spam you and we only want to send you emails that you actually want to receive. One-click unsubscribes are instantly honored.