In the following post I will show how to make use of server-side
rendering in Svelte.
Most of the time you will probably run your Svelte code client-side,
but there are scenarios where you can benefit from server-side Svelte
rendering.
SEO In my opinion the primary use case for server-side rendering is
SEO. Doing an initial rendering on the server will make your website
much more crawler accessible. Crawlers typically support some
JavaScript execution, but a complex JavaScript application is unlikely
to be indexed correctly.
My experience is that applications that make ajax calls to fetch data
are particularly challenging to index. The crawler might successfully
render the initial static part of the application, but the data will
be missing since the crawler won't make the necessary ajax calls.
Doing the initial rendering on the server allows crawlers to download
the application as fully constructed html. There is no need to execute
JavaScript on the client to compose the initial view since the view
was already built on the server.
Server-side vs Client-side Technically you could build a Svelte
application without any client side components, but it would be
completely static. Basically the application would behave much like an
old server-side PHP application. Great for crawlers, but real users
typically expect a richer user experience.
This is where server-side meets client-side.
Once the server generated html is fully rendered in the browser, we
can start a client-side counterpart of the application. The
client-side version picks up where the server side application left
off.
Both versions of the application may use the same Svelte components,
but it's important to understand that they execute independently of
each other. The server-side version does not know about the
client-side version and vice versa.
It's also important to understand that there is no default state
sharing between client and server (e.g. data property).
Article Carousel I decided to use server side Svelte to build an
article carousel for the landing page of my blog. The idea is to use a
Svelte component to cycle through articles in four of my article
categories.
The carousel should load instantly on page load, so I decided to
render the initial view on the server. Once the page has loaded I
start the client-side counterpart of the Svelte component to
dynamically cycle through the articles.
I am not known for css or design skills, so don't expect a pretty UI,
but here's how I wired everything up.
I start by creating an Article component that will be used to render
the articles in the carousel. The component should probably be split
into two components, but keeping it as one for the purposes of this
blog post.
<div class="row">
<span class="slide-show-card col-sm-3">
<h4>Angular</h4>
<h5><a class="slide-show-link" href="{{angularUrl}}">{{angularTitle}}</a></h5>
<div class="label label-success slide-show-count">Viewed {{angular.viewCount}} times</div>
<div>{{angularIntro}}</div>
</span>
<span class="slide-show-card col-sm-3">
<h4>JavaScript</h4>
<h5><a class="slide-show-link" href="{{javascriptUrl}}">{{javascriptTitle}}</a></h5>
<div class="label label-success slide-show-count">Viewed {{javascript.viewCount}} times</div>
<div>{{javascriptIntro}}</div>
</span>
<span class="slide-show-card col-sm-3">
<h4>Svelte</h4>
<h5><a class="slide-show-link" href="{{svelteUrl}}">{{svelteTitle}}</a></h5>
<div class="label label-success slide-show-count">Viewed {{svelte.viewCount}} times</div>
<div>{{svelteIntro}}</div>
</span>
<span class="slide-show-card col-sm-3">
<h4>React</h4>
<h5><a class="slide-show-link" href="{{reactUrl}}">{{reactTitle}}</a></h5>
<div class="label label-success slide-show-count">Viewed {{react.viewCount}} times</div>
<div>{{reactIntro}}</div>
</span>
</div>
<script>
class ArticleService {
constructor() {
this.articles = [];
this.index = {
'angular': -1,
'javascript': -1,
'svelte': -1,
'react': -1
};
}
getArticles() {
return fetch('/full-article-list-json')
.then((data) => {
return data.json();
})
.then((articles) => {
this.articles = articles;
return articles;
});
}
getNextArticle(key) {
this.index[key] = (this.index[key] + 1) % this.articles[key].length;
let a = this.articles[key][this.index[key]];
return a;
}
}
let articleService = new ArticleService();
export default {
onrender() {
let update = () => {
this.set({angular: articleService.getNextArticle('angular')});
this.set({javascript: articleService.getNextArticle('javascript')});
this.set({svelte: articleService.getNextArticle('svelte')});
this.set({react: articleService.getNextArticle('react')});
};
articleService.getArticles()
.then((articles) => {
update();
if(typeof global === 'undefined') {
document.querySelector('#articles').innerHTML = '';
document.querySelector('#articles-client-side').style.display =
"block";
}
})
.then(() => {
setInterval(() => {
articleService.getArticles()
.then((articles) => {
update();
});
}, 5000);
});
},
data () {
return {}
},
computed: {
angularTitle: angular => angular.title || '',
angularIntro: angular => angular.intro || '',
angularUrl: angular => `viewarticle/${angular.friendlyUrl}`,
javascriptTitle: javascript => javascript.title || '',
javascriptIntro: javascript => javascript.intro || '',
javascriptUrl: javascript => `viewarticle/${javascript.friendlyUrl}`,
svelteTitle: svelte => svelte.title || '',
svelteIntro: svelte => svelte.intro || '',
svelteUrl: svelte => `viewarticle/${svelte.friendlyUrl}`,
reactTitle: react => react.title || '',
reactIntro: react => react.intro || '',
reactUrl: react => `viewarticle/${react.friendlyUrl}`,
}
}
</script>
Server Let's take a look at the server code.
The first thing we have to do is activate the compiler by requiring
svelte/ssr/register
require( 'svelte/ssr/register' );
Next we have to require the component html file to get a handle to the
component.
We then call the render method and pass it an initial data object. The
data object is a standard Svelte data object.
app.get('/', function(req, res) {
var component = require('./app/article-show/Articles.html');
var articles = component.render({
angular: articles.angular,
svelte: articles.svelte,
react: articles.react,
javascript: articles.javascript
});
res.render('index.html', {articles: articles});
});
After calling render we get back a fully rendered component. We now
have to pass this to the node view engine.
In my case I am using Express with Mustache, so I can just pass the
component as an object to the render method. Then in my index.html
page I use Mustache view syntax with triple curly braces to render the
component on the page like so.
{{{articles}}} Client What we have so far is enough to render the
initial view of my component, but it won't support cycling through new
articles every few seconds.
To achieve this we have to start up a client-side version of the
Article component.
The client side version is loaded as a standard Svelte client-side
component.
import articles from './articles';
var articlesSection = new articles({
target: document.querySelector( 'main' ),
data: {angular: {}, javascript: {}, svelte: {}, react: {}}
});
Once the client-side version is activated we will have two components
in the DOM. As soon as the client-side version is ready to take over I
wipe out the server-side version.
There might be a more elegant way to do this, but I simply clear out
the server generated DOM element and flip a style on the client-side
version.
Navigation In addition to the article carousel I also built my main
navigation as a Svelte server side component. The nav is a pure server
side component with no client side counterpart.
The navigation is largely static, but it supports dynamic styling of
the active nav item. Here is the nav component code:
<div class="nav col-md-2 hidden-sm hidden-xs">
<a class="navLink" href="/"><div class="navBox {{home}}">Home</div></a>
<a class="navLink" href="/most-popular"><div class="navBox {{mostpopular}}">Most Popular</div></a>
<a class="navLink" href="/most-recent"><div class="navBox {{mostrecent}}">Most Recent</div></a>
<a class="navLink" href="/articleList/angular"><div class="navBox {{angular}}">Angular</div></a>
<a class="navLink" href="/articleList/react"><div class="navBox {{react}}">React</div></a>
<a class="navLink" href="/articleList/aurelia"><div class="navBox {{aurelia}}">Aurelia</div></a>
<a class="navLink" href="/articleList/javascript"><div class="navBox {{javascript}}">JavaScript</div></a>
<a class="navLink" href="/articleList/nodejs"><div class="navBox {{nodejs}}">NodeJS</div></a>
<a class="navLink" href="/articleList/vue"><div class="navBox {{vue}}">Vue</div></a>
<a class="navLink" href="/articleList/svelte"><div class="navBox {{svelte}}">Svelte</div></a>
<a class="navLink" href="/articleList/mongodb"><div class="navBox {{mongodb}}">MongoDB</div></a>
<a class="navLink" href="/articleList/unittesting"><div class="navBox {{unittesting}}">UnitTesting</div></a>
<a class="navLink" href="/articleList/dotnet"><div class="navBox {{dotnet}}">.Net</div></a>
<a class="navLink" href="/questions"><div class="navBox {{questions}}">Q&A</div></a>
<a class="navLink" href="/full-article-list"><div class="navBox {{all}}">All</div></a>
</div>
<script>
export default {
data () {
return {}
},
}
</script>
Because the nav is so static, there is no reason to regenerate the
server side html for every request. As an optimization I have decided
to cache the different variations of the nav. The only difference
between the different versions of the nav is that the "active" nav
items style is applied to different elements.
Here is some basic caching logic that ensures that we only generate
each nav version once.
function getNavComponent(key) {
key = key || 'home';
key = key.replace('-', '');
if(!navCache[key]) {
navCache[key] = createNavComponent(key);
}
return navCache[key];
}
function createNavComponent(key) {
var nav = require('./app/article-show/Navigation.html');
var data = {};
data[key] = 'activeNav';
return nav.render(data);
}
Demo If you want to test out my server-side vs client-side view you
can find it here.
I also loaded my site in Google web master tools to compare a
crawler's view of the component to a user's view
As you can tell from the screenshot my component looks pretty good to
crawlers after adding server side rendering.
Left side is the crawler view and the right side is the user view.