-
Notifications
You must be signed in to change notification settings - Fork 331
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for server-side rendering Top Stories & Comments (#47)
* index.ejs: set base href to fix asset paths for SSR views * package.json: add object-assign shim * App.js: support prebooted HTML / hydration of content * Adds hn-server-fetch using unofficial Firebase API The official Firebase API (https://github.com/HackerNews/API) requires multiple network connections to be made in order to fetch the list of Top Stories (indices) and then the summary content of these stories. Directly requesting these resources makes server-side rendering cumbersome as it is slow and ultimately requires that you maintain your own cache to ensure full server renders are efficient. To work around this problem, we can use one of the unofficial Hacker News APIs, specifically https://github.com/cheeaun/node-hnapi which directly returns the Top Stories and can cache responses for 10 minutes. In ReactHN, we can use the unofficial API for a static server-side render and then 'hydrate' this once our components have mounted to display the real-time experience. The benefit of this is clients loading up the app that are on flakey networks (or lie-fi) can still get a fast render of content before the rest of our JavaScript bundle is loaded. * server.js: add support for SSR render of top stories and comments * hn-server-fetch: add support for SSR nested comments * hn-server-fetch: remove duplicate time indication * Stories.js: display spinner for pages > 0
- Loading branch information
1 parent
ebc124c
commit f05849d
Showing
6 changed files
with
162 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
require('isomorphic-fetch') | ||
|
||
/* | ||
The official Firebase API (https://github.com/HackerNews/API) requires multiple network | ||
connections to be made in order to fetch the list of Top Stories (indices) and then the | ||
summary content of these stories. Directly requesting these resources makes server-side | ||
rendering cumbersome as it is slow and ultimately requires that you maintain your own | ||
cache to ensure full server renders are efficient. | ||
To work around this problem, we can use one of the unofficial Hacker News APIs, specifically | ||
https://github.com/cheeaun/node-hnapi which directly returns the Top Stories and can cache | ||
responses for 10 minutes. In ReactHN, we can use the unofficial API for a static server-side | ||
render and then 'hydrate' this once our components have mounted to display the real-time | ||
experience. | ||
The benefit of this is clients loading up the app that are on flakey networks (or lie-fi) | ||
can still get a fast render of content before the rest of our JavaScript bundle is loaded. | ||
*/ | ||
|
||
/** | ||
* Fetch top stories | ||
*/ | ||
exports.fetchNews = function(page) { | ||
page = page || '' | ||
return fetch('http://node-hnapi.herokuapp.com/news' + page).then(function(response) { | ||
return response.json() | ||
}).then(function(json) { | ||
var stories = '<ol class="Items__list" start="1">' | ||
json.forEach(function(data, index) { | ||
var story = '<li class="ListItem" style="margin-bottom: 16px;">' + | ||
'<div class="Item__title" style="font-size: 18px;"><a href="' + data.url + '">' + data.title + '</a> ' + | ||
'<span class="Item__host">(' + data.domain + ')</span></div>' + | ||
'<div class="Item__meta"><span class="Item__score">' + data.points + ' points</span> ' + | ||
'<span class="Item__by">by <a href="https://news.ycombinator.com/user?id=' + data.user + '">' + data.user + '</a></span> ' + | ||
'<time class="Item__time">' + data.time_ago + ' </time> | ' + | ||
'<a href="/news/story/' + data.id + '">' + data.comments_count + ' comments</a></div>' | ||
'</li>' | ||
stories += story | ||
}) | ||
stories += '</ol>' | ||
return stories | ||
}) | ||
} | ||
|
||
function renderNestedComment(data) { | ||
return '<div class="Comment__kids">' + | ||
'<div class="Comment Comment--level1">' + | ||
'<div class="Comment__content">' + | ||
'<div class="Comment__meta"><span class="Comment__collapse" tabindex="0">[–]</span> ' + | ||
'<a class="Comment__user" href="#/user/' + data.user + '">' + data.user + '</a> ' + | ||
'<time>' + data.time_ago + '</time> ' + | ||
'<a href="#/comment/' + data.id + '">link</a></div> ' + | ||
'<div class="Comment__text">' + | ||
'<div>' + data.content +'</div> ' + | ||
'<p><a href="https://news.ycombinator.com/reply?id=' + data.id + '">reply</a></p>' + | ||
'</div>' + | ||
'</div>' + | ||
'</div>' + | ||
'</div>' | ||
} | ||
|
||
function generateNestedCommentString(data) { | ||
var output = '' | ||
data.comments.forEach(function(comment) { | ||
output+= renderNestedComment(comment) | ||
if (comment.comments) { | ||
output+= generateNestedCommentString(comment) | ||
} | ||
}) | ||
return output | ||
} | ||
|
||
/** | ||
* Fetch details of the story/post/item with (nested) comments | ||
* TODO: Add article summary at top of nested comment thread | ||
*/ | ||
exports.fetchItem = function(itemId) { | ||
return fetch('https://node-hnapi.herokuapp.com/item/' + itemId).then(function(response) { | ||
return response.json() | ||
}).then(function(json) { | ||
var comments = '' | ||
json.comments.forEach(function(data, index) { | ||
var comment = '<div class="Item__kids">' + | ||
'<div class="Comment Comment--level0">' + | ||
'<div class="Comment__content">' + | ||
'<div class="Comment__meta"><span class="Comment__collapse" tabindex="0">[–]</span> ' + | ||
'<a class="Comment__user" href="#/user/' + data.user + '">' + data.user + '</a> ' + | ||
'<time>' + data.time_ago + '</time> ' + | ||
'<a href="#/comment/' + data.id + '">link</a></div> ' + | ||
'<div class="Comment__text">' + | ||
'<div>' + data.content +'</div> ' + | ||
'<p><a href="https://news.ycombinator.com/reply?id=' + data.id + '">reply</a></p>' + | ||
'</div>' + | ||
'</div>' + | ||
'</div>' | ||
comments += generateNestedCommentString(data) + '</div>' + comment | ||
}) | ||
return comments | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters