Skip to content

Commit

Permalink
added rendertron for seo
Browse files Browse the repository at this point in the history
  • Loading branch information
ademidun committed Feb 19, 2018
1 parent 5662a02 commit 134c000
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 10 deletions.
5 changes: 3 additions & 2 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ deploy () {
git push ;
# https://github.com/angular/angular-cli/issues/5618#issuecomment-348225508
npm run ng-high-mem -- build --prod;
# firebase deploy;

# ng build --prod ;
# node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng build --target=production;
# ng build --prod ; firebase deploy;
firebase deploy —-only hosting ;
firebase deploy;
# firebase deploy —-only hosting ;
return 0
}
2 changes: 1 addition & 1 deletion firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"rewrites": [
{
"source": "**",
"destination": "/index.html"
"function": "rendertron"
}
]
}
Expand Down
101 changes: 101 additions & 0 deletions functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,104 @@ admin.database().ref('/messages').push({original: original}).then(snapshot => {
res.redirect(303, snapshot.ref);
});
});

const express = require('express');
const fetch = require('node-fetch');
const url = require('url');
const app = express();


// You might instead set these as environment varibles
// I just want to make this example explicitly clear
const appUrl = 'atila-7.firebaseapp.com';
// const renderUrl = 'https://render-tron.appspot.com/render';
const renderUrl = 'https://atila-7.appspot.com/render';


// Generates the URL
function generateUrl(request) {
return url.format({
protocol: request.protocol,
host: appUrl,
pathname: request.originalUrl
});
}

function detectBot(userAgent) {
// List of bots to target, add more if you'd like

const bots = [
// crawler bots
'googlebot',
'bingbot',
'yandexbot',
'duckduckbot',
'slurp',
// link bots
'twitterbot',
'facebookexternalhit',
'linkedinbot',
'embedly',
'baiduspider',
'pinterest',
'slackbot',
'vkShare',
'facebot',
'outbrain',
'W3C_Validator'
]

const agent = userAgent.toLowerCase()

for (const bot of bots) {
if (agent.indexOf(bot) > -1) {
console.log('bot detected', bot, agent)
return true
}
}

console.log('no bots found')
return false

}


app.get('*', (req, res) => {


const isBot = detectBot(req.headers['user-agent']);


if (isBot) {

const botUrl = generateUrl(req);
// If Bot, fetch url via rendertron

fetch(`${renderUrl}/${botUrl}`)
.then(res => res.text() )
.then(body => {

// Set the Vary header to cache the user agent, based on code from:
// https://github.com/justinribeiro/pwa-firebase-functions-botrender
res.set('Cache-Control', 'public, max-age=300, s-maxage=600');
res.set('Vary', 'User-Agent');

res.send(body.toString())

});

} else {


// Not a bot, fetch the regular Angular app
// Possibly faster to serve directly from from the functions directory?
fetch(`https://${appUrl}`)
.then(res => res.text())
.then(body => {
res.send(body.toString());
})
}

});

exports.rendertron = functions.https.onRequest(app);
15 changes: 15 additions & 0 deletions src/app/_services/seo.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';

import { SeoService } from './seo.service';

describe('SeoService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [SeoService]
});
});

it('should be created', inject([SeoService], (service: SeoService) => {
expect(service).toBeTruthy();
}));
});
45 changes: 45 additions & 0 deletions src/app/_services/seo.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Injectable } from '@angular/core';

import { Meta } from '@angular/platform-browser';
// https://angularfirebase.com/lessons/seo-angular-part-1-rendertron-meta-tags/#Setting-Social-Media-Meta-Tags-in-Angular
@Injectable()
export class SeoService {

constructor(private meta: Meta) { }

generateTags(config) {
/*
// default values
config = {
title: 'Atila | Automated Scholarships. The Right Way.',
description: 'Automatically find and apply to scholarships at the click of a button. Learn and share information about education, career and life.',
image: 'https://firebasestorage.googleapis.com/v0/b/atila-7.appspot.com/o/public%2Fatila-image-preview-nov-24-2.png?alt=media&token=f4bb94ac-60f6-451a-a3df-f2300d92818d',
slug: '',
};
*/

config.title = config.title + ' - Atila';
this.meta.updateTag({ name: 'twitter:card', content: 'summary' });
this.meta.updateTag({ name: 'twitter:site', content: '@atilatech' });
this.meta.updateTag({ name: 'twitter:title', content: config.title });
this.meta.updateTag({ name: 'twitter:description', content: config.description });
this.meta.updateTag({ name: 'twitter:image', content: config.image });

this.meta.updateTag({ property: 'og:type', content: 'article' });
this.meta.updateTag({ property: 'og:site_name', content: 'Atila' });
this.meta.updateTag({ property: 'og:title', content: config.title });
this.meta.updateTag({ property: 'og:description', content: config.description });
this.meta.updateTag({ property: 'og:image', content: config.image });
this.meta.updateTag({ property: 'og:url', content: `https://atila.ca/${config.slug}` });


this.meta.updateTag({ itemprop: 'name', content: config.title });
this.meta.updateTag({ itemprop: 'description', content: config.description });
this.meta.updateTag({ name: 'description', content: config.description });
this.meta.updateTag({ itemprop: 'image', content: config.image });
this.meta.updateTag({ itemprop: 'og:url', content: `https://atila.ca/${config.slug}` });


}
}
3 changes: 3 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import { AutocompleteComponent } from './autocomplete/autocomplete.component';
import { TypeaheadComponent } from './_shared/typeahead/typeahead.component';
import { EditProfileModalComponent } from './edit-profile-modal/edit-profile-modal.component';
import { FroalaEditorModule, FroalaViewModule } from 'angular-froala-wysiwyg';
import { SeoService } from './_services/seo.service';

@NgModule({
declarations: [
Expand Down Expand Up @@ -170,6 +171,8 @@ import { FroalaEditorModule, FroalaViewModule } from 'angular-froala-wysiwyg';
AuthGuard,
GoogleAnalyticsEventsService,
SearchService,
SeoService,

{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
Expand Down
15 changes: 14 additions & 1 deletion src/app/blog-post-detail/blog-post-detail.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {Meta, Title} from '@angular/platform-browser';
import { AuthService } from "../_services/auth.service";
import {MatSnackBar} from '@angular/material';
import {MyFirebaseService} from '../_services/myfirebase.service';
import {SeoService} from '../_services/seo.service';


@Component({
Expand All @@ -44,6 +45,7 @@ export class BlogPostDetailComponent implements OnInit {
public metaService: Meta,
public authService: AuthService,
public firebaseService: MyFirebaseService,
public seoService: SeoService,
) {
this.userId = parseInt(this.authService.decryptLocalStorage('uid'));
}
Expand All @@ -54,7 +56,18 @@ export class BlogPostDetailComponent implements OnInit {
res => {
this.blogPost = (<any>res).blog;

this.updateMeta();
//this.updateMeta();
try {
this.seoService.generateTags({
title: this.blogPost.title,
description: this.blogPost.description,
image: this.blogPost.header_image_url,
slug: `blog/${this.blogPost.user.username}/${this.blogPost.slug}`
});
}
catch (err) {
console.log('seoService Error', err);
}

this.titleService.setTitle(this.blogPost.title);
if (! isNaN(this.userId)){
Expand Down
20 changes: 18 additions & 2 deletions src/app/forum-detail/forum-detail.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { NgZone } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { AuthService } from "../_services/auth.service";
import {MatSnackBar} from '@angular/material';
import {SeoService} from '../_services/seo.service';
@Component({
selector: 'app-forum-detail',
templateUrl: './forum-detail.component.html',
Expand All @@ -39,17 +40,32 @@ export class ForumDetailComponent implements OnInit {
public commentService: CommentService,
public forumService: ForumService,
public authService: AuthService,
public snackBar: MatSnackBar,) {
public snackBar: MatSnackBar,
public seoService: SeoService,) {
this.userId = parseInt(this.authService.decryptLocalStorage('uid'));
}

ngOnInit() {

let defaultProfileImage = 'https://firebasestorage.googleapis.com/v0/b/atila-7.appspot.com/o/user-profiles%2Fgeneral-data%2Fdefault-profile-pic.png?alt=media&token=455c59f7-3a05-43f1-a79e-89abff1eae57';
let atilaImage = 'https://firebasestorage.googleapis.com/v0/b/atila-7.appspot.com/o/public%2Fatila-image-preview-nov-24-2.png?alt=media&token=f4bb94ac-60f6-451a-a3df-f2300d92818d"';
this.forumService.getBySlug(this.route.snapshot.params['slug']).subscribe(
forum => {
this.forum = forum;
try {
this.seoService.generateTags({
title: this.forum.title,
description: this.forum.starting_comment.text,
image: this.forum.user.profile_pic_url == defaultProfileImage ? atilaImage :this.forum.user.profile_pic_url,
slug: `forum/${this.forum.slug}/`
});
}
catch (err) {
console.log('seoService Error', err);
}
this.forum.starting_comment = null;
this.titleService.setTitle('Atila Forum - ' + this.forum.title);


this.commentService.getComments(this.forum.id,'Forum').subscribe(
res => {

Expand Down
16 changes: 14 additions & 2 deletions src/app/scholarship-detail/scholarship-detail.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { AuthService } from "../_services/auth.service";
import {Meta, Title} from '@angular/platform-browser';
import {MyFirebaseService} from '../_services/myfirebase.service';
import {UserProfile, addToMyScholarshipHelper} from '../_models/user-profile';
import {SeoService} from '../_services/seo.service';


@Component({
Expand Down Expand Up @@ -53,6 +54,7 @@ export class ScholarshipDetailComponent implements OnInit {
public authService: AuthService,
public metaService: Meta,
public firebaseService: MyFirebaseService,
public seoService: SeoService
) {
// Get the id that was passed in the route
this.scholarshipSlug = route.snapshot.params['slug'];
Expand All @@ -63,17 +65,26 @@ export class ScholarshipDetailComponent implements OnInit {

ngOnInit() {
// Load scholarship from the id

this.scholarshipService.getBySlug(this.scholarshipSlug)
.subscribe(
scholarship => {

this.scholarship = scholarship;

this.seoService.generateTags({
title: this.scholarship.name,
description: this.scholarship.description,
image: this.scholarship.img_url,
slug: `scholarship-detail/${this.scholarship.slug}/`
});

if ('2019-01-01T00:00:00Z' == this.scholarship.deadline) {
this.scholarship['metadata']['deadline_tbd'] = 'TBD';
}

this.titleService.setTitle('Atila - ' + this.scholarship.name);
this.updateMeta();
//this.updateMeta();
// Get the user profile of the scholarship owner
if (this.scholarship.owner){
this.userProfileService.getById(scholarship.owner)
Expand Down Expand Up @@ -362,7 +373,7 @@ export class ScholarshipDetailComponent implements OnInit {
return comment.up_votes_count;
}
}

/*
updateMeta(){
const fullUrl = document.location.href;
Expand Down Expand Up @@ -441,4 +452,5 @@ export class ScholarshipDetailComponent implements OnInit {
);
}
*/
}
4 changes: 2 additions & 2 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
]}
</script>

<meta itemprop="name" content="Atila | Automated Scholarships. The Right Way.">
<meta itemprop="description" content="Automatically find and apply to scholarships at the click of a button. Learn and share information about education, career and life.">
<meta itemprop="name" content="Atila | Automated Scholarships. The Right Way,">
<meta itemprop="description" content="Automatically find, and apply to scholarships at the click of a button. Learn and share information about education, career and life.">
<meta itemprop="image" content="https://firebasestorage.googleapis.com/v0/b/atila-7.appspot.com/o/public%2Fatila-image-preview-nov-24-2.png?alt=media&token=f4bb94ac-60f6-451a-a3df-f2300d92818d">

<meta property="og:title" content="Atila | Automated Scholarships. The Right Way." />
Expand Down

0 comments on commit 134c000

Please sign in to comment.