181

What's the best practice of using Twitter Bootstrap, refer to it from CDN or make a local copy on my server?

Since Bootstrap keeps evolving, I am afraid if I refer to the CDN, the user would see different webpages over time, and some tags may even broken. What's most people's choice?

shapeare
  • 4,133
  • 7
  • 28
  • 39

5 Answers5

232

Why Not Both ¯\_(ツ)_/¯ ? Scott Hanselman has a great article on using a CDN for performance gains but gracefully falling back to a local copy in case the CDN is down.

Specific to bootstrap, you can do the following to load from a CDN with a local fallback:

Working Demo in Plunker

<head>
  <!-- Bootstrap CSS CDN -->
  <link rel="stylesheet" href="~https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css">
  <!-- Bootstrap CSS local fallback -->
  <script>
    var test = document.createElement("div")
    test.className = "hidden d-none"

    document.head.appendChild(test)
    var cssLoaded = window.getComputedStyle(test).display === "none"
    document.head.removeChild(test)

    if (!cssLoaded) {
        var link = document.createElement("link");

        link.type = "text/css";
        link.rel = "stylesheet";
        link.href = "lib/bootstrap.min.css";

        document.head.appendChild(link);
    }
  </script>
</head>
<body>
    <!-- APP CONTENT -->

    <!-- jQuery CDN -->
    <script src="~https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
    <!-- jQuery local fallback -->
    <script>window.jQuery || document.write('<script src="lib/jquery.min.js"><\/script>')</script>

    <!-- Bootstrap JS CDN -->
    <script src="~https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <!-- Bootstrap JS local fallback -->
    <script>if(typeof($.fn.modal) === 'undefined') {document.write('<script src="lib/bootstrap.min.js"><\/script>')}</script>
</body>

Updates

Best Practices

To your question on Best Practices, there are a lot of very good reasons to use a CDN in a production environment:

  1. It increases the parallelism available.
  2. It increases the chance that there will be a cache-hit.
  3. It ensures that the payload will be as small as possible.
  4. It reduces the amount of bandwidth used by your server.
  5. It ensures that the user will get a geographically close response.

To your versioning concern, any CDN worth its weight in salt with let you target a specific version of the library so you don't accidentally introduce breaking changes with each release.

Using document.write

According to the mdn on document.write

Note: as document.write writes to the document stream, calling document.write on a closed (loaded) document automatically calls document.open, which will clear the document.

However, the usage here is intentional. The code needs to be executed before the DOM is fully loaded and also in the correct order. If jQuery fails, we need to inject it into the document inline before we attempt to load bootstrap, which relies on jQuery.

HTML Output After Load:

Example Output

In both of these instances though, we're calling while the document is still open so it should inline the contents, rather than replacing the entire document. If you're waiting till the end, you'll have to replace with document.body.appendChild to insert dynamic sources.

Aside: In MVC 6, you can do this with link and script tag helpers

KyleMit
  • 30,350
  • 66
  • 462
  • 664
  • 1
    Hardcoding `rgb(51, 51, 51)` seems risky - what if someone changes the colour and forgets to update it? Is there a more stable property one could use? – Flash Feb 19 '17 at 02:48
  • @Flash, Yeah, I agree that seems finicky. It's hard to test for CSS changes in global javascript variables or via the CSS directly. We just have to test elements to see if they've been styled the way that the CSS is likely to describe them, and we'll always have a `` element. [This answer](http://stackoverflow.com/a/17232679/1366033) adds some markup with a `.hidden` div and then does a test to see if it's visible: `$('#bootstrapCssTest').is(':visible')`. That class is probably much less likely to have breaking changes over time. – KyleMit Feb 21 '17 at 15:48
  • @KyleMit, How can I do this for [Google Material Icons](https://material.io/icons)? – Rana Depto Mar 03 '18 at 07:52
  • @RanaDepto, firstly, you'll have to implement both the [Google Web Fonts CDN setup](http://google.github.io/material-design-icons/#setup-method-1-using-via-google-web-fonts) and the [self hosting setup](http://google.github.io/material-design-icons/#setup-method-2-self-hosting). When testing if JS has loaded, you can check for any global variable, when [testing if CSS has loaded](https://stackoverflow.com/q/7383163/1366033), you have to figure out a style that *would* have been applied and test for that... it's not perfect, but it's what we have. That should be enough to get you started. – KyleMit Mar 03 '18 at 15:26
  • 4
    Great answer! Only a note: if you are using Bootstrap 4, you should use "d-none" class instead of "hidden" in order to let fail over works. – deste Jun 29 '18 at 21:54
  • JS noob here. Using `document.write` just clears the DOM. How would I append this to the end of the body instead with JS? – jarrodwhitley Apr 16 '19 at 16:44
  • 1
    @JarrodW. - great question. I had to do some digging. we should be good to use it here - see updated answer – KyleMit Apr 18 '19 at 14:59
  • @KyleMit yeah...I definitely was calling it at the end of the body tag lol – jarrodwhitley Apr 19 '19 at 20:39
  • @KyleMit One question here - Should I copy all files and folders from bootstrap downloaded zip or it would be sufficient to have bootstrap.min.js and bootstrap.min.css only? – Muhammad Tarique Mar 02 '20 at 11:33
11

Depends on the specific site.

Do you have many users? Do you care about bandwidth usage? Is performance an issue (CDN's can speed up the responses) ?

You can link to a specific version:

//maxcdn.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css

Or

//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css

That way you don't have to worry about library updates, its a better practice to keep updated.

I am not sure what are the exact statistics about developers choice, but you can have a look here and see Billions of requests are sent to Bootstrap CDN, which means it is robust and safe to use.

its4zahoor
  • 1,709
  • 1
  • 16
  • 23
Ofiris
  • 6,047
  • 6
  • 35
  • 58
7

Almost all public CDNs are pretty reliable. However, if you are worried about that fraction of the time when a CDN might be down, you can load Bootstrap from one Bootstrap CDN, and fallback to an alternative CDN in case the first one is down.

<html>
  <head>
    <!-- Bootstrap CSS CDN with Fallback -->
    <link rel="stylesheet" href="https://pagecdn.io/lib/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha256-YLGeXaapI0/5IgZopewRJcFXomhRMlYYjugPLSyNjTY=" crossorigin="anonymous">
    <script>
    var test = document.createElement("div")
    test.className = "hidden d-none"

    document.head.appendChild(test)
    var cssLoaded = window.getComputedStyle(test).display === "none"
    document.head.removeChild(test)

    if (!cssLoaded) {
        var link = document.createElement("link");

        link.type = "text/css";
        link.rel = "stylesheet";
        link.href = "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css";

        document.head.appendChild(link);
    }
    </script>
  </head>
  <body>
    <!-- APP CONTENT -->

    <!-- jQuery CDN with Fallback -->
    <script src="https://pagecdn.io/lib/jquery/3.2.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
    <script>window.jQuery || document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"><\/script>');</script>

    <!-- Bootstrap JS CDN with Fallback -->
    <script src="https://pagecdn.io/lib/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha256-CjSoeELFOcH0/uxWu6mC/Vlrc1AARqbm/jiiImDGV3s=" crossorigin="anonymous"></script>
    <script>if(typeof($.fn.modal) === 'undefined') {document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"><\/script>')}</script>
  </body>
</html>

About your second concern: The links in this post are hard coded versions of bootstrap and jquery. So, even if the bootstrap and jquery libraries are constantly developed and get new features, your site will stay the same over time.

Hamid Sarfraz
  • 1,089
  • 1
  • 14
  • 34
4

I tried to edit the KyleMit's answer but the forum was marking as a wrong indented code, even it wasn't, so I'm adding my contribution right bellow:

As the question is tagged as a topic (and not only ), maybe it's helpful to update the response for the newer version of Bootstrap.

As the framework added a new class for hiding elements on its fourth version, we should use .d-none instead of .hidden in this case.

Everything else remains the same on that case, except the lib version (of course!)

André C. Rocha
  • 301
  • 5
  • 17
4

Thanks to @KyleMit. Another way of fall back is using 'window' object as under -

<script type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
<script>
window.jQuery || document.write("<script src='js/jquery.min.js'><\/script>");
</script>

It works like this - If the CDN link works, 'window' object will have 'jQuery' property available else the second part of the script i.e. document.write will get executed which points to the local copy.

Answer to original question - Having CDN has many benefits such as quick downloads without impacting your server and bandwidth. Having a local copy has its own benefit (as a fall back arrangements). On intranet, due to proxy settings, security policies, CDN link may not work or if CDN link is down it may not work. The straight answer is to have both.

Anand
  • 173
  • 2
  • 9