Can I somehow produce "initial" HTML files using Svelte?
I am using Django, Webpack and Tailwindcss. I want to use Svelte with my frontend, but I don't want to give up the speed that comes with simply using server-side rendering (Django templates). If what I present initially is a bootstrap HTML page that pulls in the bundle.js and Svelte builds the DOM on the client side, then the browsers only starts downloading images only after the JS file is loaded.
Compare this to having the initially rendered HTML that already contains links to images, the browser starts downloading them alongside the JS making for faster perceived page loading.
I do not want to use Sapper as my application server, I want to continue using Django.
The challenge is sharing state (props) between your Django app and the Svelte components.
To get the HTML code from a component:
require('svelte/register')
const MyComponent = require('./MyComponent.svelte').default
const { html } = MyComponent.render({ ...props... })
If the components has no props, you can compile and cache the HTML templates (maybe even before runtime).
If you want to send props dynamically, for example based on data in your database, then you need to do so at runtime. That means executing JS server-side. Performance won't be bad if you cache the result.
If you can't cache, then using Django for performance would be negated, because you'd be executing Svelte anyways, so might as well use Svelte to do the whole server-side job, and then use Django as a backend server.
According to this blog post you do the following instructions:
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.
Related
I am having a strange issue with the bootstrap toolkit inside ngFor. On hover, it is taking some time to display the toolkit title and CSS is not getting applied. Please find the screenshot for the same. And if the toolkit is used outside ngFor, it works fine. It works normally.
Here is my code
.ts
ngOnInit(): void {
jQuery(function() {
(jQuery('[data-toggle="tooltip"]') as any).tooltip();
});
this.getGroupsForBox();
}
async getGroupsForBox() {
this.groups = await this.boxManagementService.findGroupsOfBox(this.hub.id);
}
.html
<div *ngFor="let group of groups">
<span
class="badge badge-info"
data-toggle="tooltip"
data-placement="bottom"
title="{{ group.GroupDescription }}"
>{{ group.GroupName }}</span
>
<span> </span>
</div>
*ngFor is structural directive, it creates html DOM on the fly when get the data. That's how it work, ideally you should get ride of jQuery and use the angular bootstrap library.
How ever you can achieve that, you just need to make sure to execute jQuery method after that *ngFor completed the rendering off all the items in the list. Than only you should do that.
code.ts
ngOnInit(): void {
// right now in ngOnInit, nothing is there in the DOM it doesn't applied to that
// Remove this from here.
// jQuery(function() {
// (jQuery('[data-toggle="tooltip"]') as any).tooltip();
// });
this.getGroupsForBox();
}
async getGroupsForBox() {
this.groups = await this.boxManagementService.findGroupsOfBox(this.hub.id);
// once you know list are populated in html then do that part
// getting the data in TS and rendering in html there will be time difference in ms, use small hack of setTimeout not ideal not recommended but it will do the job
setTimeout(()=>{
jQuery(function() {
(jQuery('[data-toggle="tooltip"]') as any).tooltip();
});
},500);
}
I just wonder if there was a new way to provide an initial data (json) for client side processing? I'm using ASP.NET Core MVC and I try to avoid an initial ajax request for data.
Currently I can render the Model into a json data within my asp.net view:
<script>
var serverData = #Json.Serialize(Model); //List<TextItem>
</script>
Then I can access that serverData in my client script (VueJs, .vue component file):
<template>
<ul>
<li v-for="item in list">{{item.text}}</li>
</ul>
</template>
<script>
export default {
data: function () {
return {
list: serverData
}
}
};
</script>
Solution works fine but I don't really like to render raw data into the page source. Is there any more elegant way to provide an inital data without any additinal (ajax) request to the server?
Edit:
I found an article about the concept (it uses angular):
https://blog.mariusschulz.com/2014/03/25/bootstrapping-angularjs-applications-with-server-side-data-from-aspnet-mvc
Is it still the best way to pre-render data on server side?
I am using FlowRouter as a router for a Meteor/React application I am trying to create. I'm having a very hard time trying to get my react components to render in specific places. Does anyone know how to do this?
So on my landing page, when I click a button, I want to route to a secondary page. I have three different components that I want to render in certain parts of the page. I've been using ReactLayout.render(), but I can't seem to make sure components get rendered in certain areas. I thought document.getElementById would work
ReactLayout.render(LandingPage, document.getElementById("landing-page")
but it hasn't been.
The second parameter of ReactLayout.render expects an object. If you want to render several components into your LandingPage element, it might look something like this:
LandingPage = React.createClass({
render() {
return (
<div className="app-root">
<AppHeader />
<div className="container">
{this.props.testOne}
</div>
<div className="app-root">
{this.props.testTwo}
</div>
</div>
);
}
});
Then render using:
FlowRouter.route( '/testRedirect', {
name: 'test',
action() {
ReactLayout.render( Default, { testOne: <TestOneComponent />, testTwo: <TestTwoComponent /> } );
}
});
I'm using Meteor 0.8 with Blaze and I want to attach events dynamically to HTML contents generated using UI.toHTML of a template. The functionality I am looking for is the alternative to Spark.attachEvents in Blaze.
What I have done so far is that I have created the following template to be used like a widget/component.
<template name="postLinks">
<div id="link-popover-wrapper" >
<ul class="link-popover">
{{#each linkOptions}}
<li><a tabindex="-1" class="link-action" id="link-{{value}}" href="#">{{label}}</a>
</li>
{{/each}}
</ul>
</div>
</template>
And the template is used in Helper of the myPostItem template.
Template.myPostItem.events({
'click .post-item-link-picker': function (evt, tmpl) {
var tempData = {linkOptions:[{label:'Favorite', value : 'favorite'}, ...]};
// Get the HTML content of the template passing data
var linkContent = UI.toHTML(Template['postLinks'].extend({data: function () { return tempData; }}));
// Attach events to the linkContent like in Spark
/*Spark.attachEvents({
'click link-action': function (e, tmpl) {
alert("Component item click");
}
}, linkContent);*/
// Popover the content using Bootstrap popover function
}
});
So my requirement is to attach events to a dynamically generated HTML contents.in the linkContent like Spark.attachEvents after the following line as mentioned in above code.
var linkContent = UI.toHTML(Template['postLinks'].extend({data: function () { return tempData; }}));
Hope somebody can help to find a way to do this in Meteor 0.8 with Blaze.
The reason that Spark-generated HTML could be directly inserted into the DOM is because it had "landmarks" - annotations that could be processed into events and handlers when the DOM nodes were materialized.
Blaze works differently - it inserts a UI component into the DOM directly and attaches events, using the UI.render function. It cannot directly attach template events to the DOM if you use UI.toHTML because there are none of the annotations that Spark had for doing this.
I'm also using Bootstrap popovers in my app, and as far as I know there's no clean way to insert reactive content into a popover. However, you can approximate it with a hack in the following way:
In the content callback of the popover, render the template with UI.toHTML - a nonreactive version of the content. This is necessary because otherwise the popover won't be sized and positioned properly.
Using a Meteor.defer call, replace the popover contents with reactive content, so they'll continue updating while the popover is open.
Because Bootstrap uses jQuery, you should be fine with removing reactive logic properly, for now. Future versions of Meteor will probably have easier ways to do this.
I have an application that have four modules in the front end, I'm trying to use as much as possible AngularJs in the front end I'm using an empty website asp.net project to host all the files and the REST serviceStack, my project have kind of the following structure:
~/ (web.config, global.asax and all the out of the box structure for an asp.net website)
- App <- AngularJs
- Users <- js controllers and views (static html files)
- Companies
- BackEnd
- Public
Index.html
IndexCtrl.js
App.js
- Content
- Js
I use angularjs service calls and the backend I'm using REST with servicestack.
the question is how can I restrict the access only to authenticated users to those static html files? let's say the ones that are inside inside Companies, Backend and users for example
Hi After doing some research this is the solution that worked for me:
Install razor markdown from nuget
Change the file structure to match the default behavior RM [Razor Markdown] to /views
Modify the web config following the approach described in this service stack example
Change all the static htmls files to .cshtml files, this by default creates the same route without the extension like /views/{Pagename} without the extension, I'm just using this approach to get the authorization logic simpler to implement (at least for me)
Update the service method with an authorize attribute you can find out more in this page
to illustrate a lit of bit more this is my route definition in so far:
'use strict';
angular.module('myApp', ['myApp.directives', 'myApp.services']).config(
['$routeProvider', function($routeProvider) {
$routeProvider.when('/Dashboard', {
controller: 'dashboardCtrl',
templateUrl: 'Views/dashboard'
}).when('/Payments', {
controller: 'paymentsCtrl',
templateUrl: 'Views/payments'
}).
when('/Login', {
controller: 'loginCtrl',
templateUrl: 'Views/login'
});
}]
);
Notice that the references are pointed now to the razor paths.
this is a small menu I've done in angular
<div class="container">
<div class="navbar" ng-controller="indexCtrl">
<div class="navbar-inner">
<a class="brand" href="#/">header menu</a>
<ul class="nav">
<li ng-class="{active: routeIs('/Dashboard')}">Dashboard</li>
<li ng-class="{active: routeIs('/Login')}">Login</li>
<li ng-class="{active: routeIs('/Payments')}">payments</li>
</ul>
</div>
</div>
<ng-view></ng-view>
</div>
let's say that the payments page is restricted, so every time I click on a the page I get a 401 unauthorized message.
Service host:
public override void Configure(Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {
new FacebookAuthProvider(appSettings),
new TwitterAuthProvider(appSettings),
new BasicAuthProvider(appSettings),
new GoogleOpenIdOAuthProvider(appSettings),
new CredentialsAuthProvider()
})); //I'm going to support social auth as well.
Plugins.Add(new RegistrationFeature());
Routes.Add<UserRequest>("/Api/User/{Id}");
Routes.Add<LoginRequest>("/Api/User/login","POST");
Routes.Add<PaymentRequest>("/views/Payments");
}
I hope that helps
Create a CatchAllHander method to check for restricted routes and, for those static files that require authentication, return the ForbiddenFileHander if not authenticated, otherwise return null. Given an isAuthenticated method and restrictedDirs is defined somewhere - maybe your app or web config file, it can be as simple as:
appHost.CatchAllHandlers.Add((httpMethod, pathInfo, filePath) => {
if ( restrictedDirs.ContainsKey(pathInfo) && !isAuthenticated())
return new ForbiddenHttpHandler();
return null;
});
Why not use Forms Authentication? Simply add a few < location > tags to your web.config to allow/disallow different sections, you can even do it based on roles.