CINXE.COM

Client-Side vs. Server-Side Rendering

<!DOCTYPE html> <html lang="en-US"> <title>Client-Side vs. Server-Side Rendering</title> <meta charset="utf-8"> <meta name=referrer content=origin> <meta name=viewport content="width=device-width,initial-scale=1"> <meta name="description" content="Karl Seguin's Blog - A mix of coding and creative writing" /> <style> * {margin: 0; padding: 0; } *, *:before, *:after {box-sizing: border-box;} main {width: 800px;margin: 20px auto;} #byline{color: #555;font-size: 90%;margin: 10px 0} body{background:#fff;font-family:"Helvetica Neue", sans-serif;font-size: 18px;padding: 0; margin: 0} body > a {background:#444;color:#fff;padding:10px;text-decoration:none;display: inline-block;position: absolute;top: 0;right: 0} ol, ul {margin-left: 30px;} li {line-height: 30px;} p {margin: 16px 0;line-height: 32px;} blockquote{line-height: 32px; border-left: 10px solid #ddd; margin: 0; padding-left: 25px} aside{padding: 0 10px; border-radius: 4px;background:#fffefa;border: 1px solid #b1dfe1;} h1, h2, h3 {outline: 0;margin: 30px 0 5px;} h1 {font-size: 28px;} h2 {font-size: 24px;} h3 {font-size: 20px;} h1 a, h2 a, h3 a, h4 a, h5 a { color: #000; text-decoration: none } h1 a:before, h2 a:before, h3 a:before {width: 1em;content: "搂";color: #999;display: none;text-decoration: none;margin-left: -1em;} h1 a:hover:before, h2 a:hover:before, h3 a:hover:before {display: inline-block;} p code, li code {padding: 0px 3px;border-radius: 5px;background: #fffce3;border: 1px solid #e9e9e5;} code br {border: 1px solid red} .pager {display: flex;} .pager:last-of-type {margin-top: 50px;} .pager span, .pager a {flex: 50%;line-height:50px;} .pager a {color: #fd25a0; padding: 0 10px; border-radius: 4px; border: 1px solid #ccc; text-decoration: none; } .pager a:hover{background: #eee; border-color: #bbb;} .pager .prev {margin-right: 10px;} .pager a.prev:not(:empty):before{content: '芦 '} .pager a.next:not(:empty):after{content: ' 禄'} .pager .next {text-align: right;margin-left: 10px} /* https://raw.githubusercontent.com/PrismJS/prism-themes/refs/heads/master/themes/prism-one-light.css */ code[class*=language-],pre[class*=language-]{font-size: 16px;background:#f9f9f9;color:#383942;font-family:"Fira Code","Fira Mono",Menlo,Consolas,"DejaVu Sans Mono",monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;line-height:1.5;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection{background:#e5e5e5;color:inherit}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection{background:#e5e5e5;color:inherit}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-]{padding:.2em .3em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.prolog{color:#9fa0a6}.language-css .token.property,.language-json .token.operator,.language-markdown .token.url,.language-markdown .token.url-reference.url>.token.string,.language-markdown .token.url>.token.operator,.token.attr-value>.token.punctuation.attr-equals,.token.doctype,.token.entity,.token.punctuation,.token.special-attr>.token.attr-value>.token.value.css{color:#383942}.language-json .token.null.keyword,.language-markdown .token.bold .token.content,.token.atrule,.token.attr-name,.token.boolean,.token.class-name,.token.constant,.token.number{color:#b66a00}.language-css .token.atrule .token.rule,.language-css .token.important,.language-javascript .token.operator,.language-markdown .token.italic .token.content,.token.keyword{color:#a625a4}.language-css .token.selector,.language-markdown .token.list.punctuation,.language-markdown .token.strike .token.content,.language-markdown .token.strike .token.punctuation,.language-markdown .token.title.important>.token.punctuation,.token.deleted,.token.important,.token.property,.token.symbol,.token.tag{color:#e35549}.language-css .token.url>.token.string.url,.language-markdown .token.code-snippet,.token.attr-value,.token.attr-value>.token.punctuation,.token.builtin,.token.char,.token.inserted,.token.regex,.token.selector,.token.string{color:#50a04f}.language-markdown .token.url>.token.content,.token.function,.token.operator,.token.variable{color:#4078f1}.language-css .token.function,.language-css .token.url>.token.function,.language-markdown .token.url-reference.url,.language-markdown .token.url>.token.url,.token.url{color:#0083bb}.language-javascript .token.template-string>.token.interpolation>.token.interpolation-punctuation.punctuation{color:#c91142}.language-markdown .token.blockquote.punctuation,.language-markdown .token.hr.punctuation{color:#9fa0a6;font-style:italic}.token.bold{font-weight:700}.token.comment,.token.italic{font-style:italic}.token.entity{cursor:help}.token.namespace{opacity:.8} @media screen and (max-width: 1000px) { .pager{flex-direction: column;} .pager .home {flex: auto} .pager a{text-align: center !important; margin: 0 !important} .pager .next{} body > a {position: relative;} main{width: 100%; padding: 10px} code[class*="language-"], pre[class*="language-"], pre{margin-left: 0} } </style> <link href=/atom.xml rel=alternate type=application/atom+xml> <a href=/>home</a> <main> <article> <h1>Client-Side vs. Server-Side Rendering</h1> <div id=byline>May 30, 2012</div> <p>Yesterday Twitter announced that it was <a href="http://engineering.twitter.com/2012/05/improving-performance-on-twittercom.html">moving away from client-side rendering back to server-side rendering</a> in order to improve page load time. Today I found myself having to defend my position that server-side rendering will almost always be faster. I figured I'd blog about it.</p> <p>I want to point out a couple things. First, I'm talking specifically about render performance and page speed. There might be other compelling advantages to thick-clients; I'm talking about performance. Secondly, I'm going to get on a high horse here and say that it <strong>worries me that developers think client-side rendering is faster</strong>. This is basic and fundamental knowledge about how the web and browsers work. Maybe I'll be proven wrong. If I am, I'll admit it. It'll be embarrassing because it means that I don't know the fundamentals. But I'll be glad to have learned (which is why I blog).</p> <h3>How It Works</h3> <p>With client-side rendering, your initial request loads the page layout, CSS and JavaScript. It's all common except that some or all of the content isn't included. Instead, the JavaScript makes another request, gets a response (likely in JSON), and generates the appropriate HTML (likely using a templating library).</p> <p>With server-side rendering, your initial request loads the page, layout, CSS, JavaScript and content.</p> <p>For subsequent updates to the page, the client-side rendering approach repeats the steps it used to get the initial content. Namely, JavaScript is used to get some JSON data and templating is used to create the HTML.</p> <p>Updates using server-side rendering is where a lot of developers start going off the deep end. They actually think <em>page refresh</em>. Instead, what I thought we've all been doing for the last half decade, is some form of: </p> <pre class="language-javascript"><code class="language-javascript"><span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#loadTweets'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> $<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/tweets/person'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token literal-property property">last_id</span><span class="token operator">:</span> <span class="token number">239393939</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#tweets'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">prepend</span><span class="token punctuation">(</span>r<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> e<span class="token punctuation">.</span><span class="token function">preventDefaults</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>In other words, we are still only doing a partial update, but letting the server do the rendering and inserting that finalized output into our DOM.</p> <p>So those are the two workflows. Let's see why client-side rendering is slower.</p> <h3>Initial Load</h3> <p>Comparing the initial flow of the two approaches, it should be obvious that client-side rendering is going to be slower. It requires more JavaScript to be downloaded, which is more JavaScript to parse. It requires a 2nd HTTP request to load the content, and then requires more JavaScript to generate the template. Even if the initial JavaScript gets cached, it still needs to get parsed, and the 2nd request isn't going to happen until the document is loaded.</p> <p>I could see bootstrapping the initial request to include the initial data as a JavaScript object. But I don't see many frameworks advocating this approach. I'm not sure I like it. And it would still be more JavaScript to download, parse and more cycles on the CPU to render it.</p> <h3>Control</h3> <p>One of the biggest problem with client-side rendering is that you lose control over the experience. Developers are building sites with 8-core boxes and 16 gigs of ram, running the latest OS and latest non-IE browser. With the site running locally. They think "geez, this is fast!" Meanwhile I'm trying to load your site on my horrible Samsung Galaxy S or my underpowered air.</p> <p>Parsing JavaScript is slow..especially on some still popular browsers. Even on modern browsers, parsing some JavaScript is going to be slower than parsing less JavaScript.. This is especially true when you consider mobile devices. If you do rendering on the server, you have a lot more control over how fast and how consistent that rendering is. Overloaded? Buy more hardware. The client still has to render the HTML (it has to do this either way), but it doesn't have the extra JavaScript and templating overhead.</p> <h3>Caching</h3> <p>If your end points only return JSON, then all you'll be able to cache on the backend is JSON. The client will always have to spend time building the HTML elements from that data. If your end points return HTML, you can cache that instead. By rendering on the server, you can cache the final shape of your data. So not only does your client not have to generate templates, your server doesn't have to either.</p> <p>And while we are at it, I want to point out that generating JSON on the server isn't a no-op operation. I'll admit that rendering JSON is probably quicker than rendering an ERB template, but people talk about it as though it just magically happens for free. (Although, if you are caching the HTML template, you can certainly cache the JSON as well).</p> <h3>Bandwidth</h3> <p>With client-side rendering your initial load will be and feel heavier: again, more JavaScript and a 2nd request. However, subsequent updates will require less bandwidth. JSON is pretty verbose, but it's probably less verbose than HTML with classes and ids. This is an area where client-side rendering will be faster (if we ignore the fact that we client-side rendering still needs to spend time transforming the JSON to HTML).However, both HTML and JSON should compress quite well.</p> <p>I'm legitimately drawing a blank trying to come up with a pattern where the JSON data would be significantly smaller. If it's a collection of data (like search results), it'll just be the same divs with the same class name...much like it'll be the same JSON fields. I guess client-side rendering might have a real edge if you are using Word to generate your html...</p> <h3>A single API/Endpoint</h3> <p>I know I said I'd only talk about performance, but one argument that often gets brought up is that by consuming JSON, the browser is just another consumer your public API. The result is a single endpoint and clean routes.</p> <p>Your server-side framework should let you respond to different requests types with minimal effort. In Rails this is done via <a href="http://apidock.com/rails/ActionController/MimeResponds/InstanceMethods/respond_to">respond_to</a>. If your framework doesn't support something similar, either build it, or change frameworks.</p> <h3>Conclusion</h3> <p>Only considering performance, should you ever use client-side rendering? There's one obvious scenario where it makes sense: when you render based on existing data. That is, if you don't need to go to the server to render, say because you are going to display known data in a different perspective, client-side rendering makes sense.</p> <p>Otherwise, client-side rendering requires a heavier initial load with a 2nd request, not being able to cache the final output and greater dependency on slower CPUs and rendering engines. Any one of those is a going to make client-side rendering slower. Combine them? Well, Twitter's server-side rendering takes 1/5 the time as client-side rendering.</p> <p>If you are interested in learning more, I suggest you also check out <a href="http://37signals.com/svn/posts/3112-how-basecamp-next-got-to-be-so-damn-fast-without-using-much-client-side-ui">How Basecamp Next was built to be so fast</a>.</p> </article> </main>

Pages: 1 2 3 4 5 6 7 8 9 10