Sì, questo è possibile e facile utilizzando Meteor 1.3, alcuni pacchetti aggiuntivi e un hack minore.
Vedere bc-real-estate-math.com per un esempio. (questo sito segna solo 97 perché non ho ridimensionato le immagini e Analytics e FB hanno vita cache breve)
Tradizionalmente, una piattaforma resa client come Meteor era lenta sui primi carichi con una cache vuota a causa del grande carico utile di Javascript. Il rendering lato server (usando React) della prima pagina lo risolve quasi eccetto che Meteor out-of-the-box non supporta JavaScript asincrono o CSS in linea rallentando così il tuo primo rendering e uccidendo il tuo punteggio di Google PageSpeed Insights (e argomenta come potresti avere a che fare con questo parametro, influisce sui prezzi di AdWord dei miei clienti e quindi lo ottimizzo).
Questo è quello che si può ottenere con l'installazione di questa risposta:
- molto rapido time-to-first-render su Cache vuota, come 500ms
- No "lampo di contenuti in stile"
- Punteggio 100/100 su Google PageSpeed Insights
- Uso di qualsiasi webfont senza uccidere il tuo punteggio PageSpeed
- Controllo SEO completo incluso titolo della pagina e meta
- Perfetta integrazione con Google Analytics e Facebook Pixel che registra con precisione ogni visualizzazione della pagina indipendentemente dal server o lato client di rendering
- Google script di ricerca bot e altri crawler vedere tutto di beni HTML delle pagine immediatamente senza correre
- gestisce senza soluzione di continuità URL #hash per scorrere fino a parti di una pagina
- utilizzare un numero piccolo (come < 30) di icona di carattere caratteri senza aggiungere richieste o male velocità segnare
- Scala fino a tutto il formato di Javascript, senza impactin g pagina di destinazione esperienza
- Tutta la suggestione regolare di un pieno Meteor web-app
Ciò che questa impostazione non può raggiungere:
- grandi quadri CSS monolitici cominceranno a uccidere il tuo punteggio Page Speed e rallenta il time-to-first-rendering. Bootstrap è grande quanto puoi prima di iniziare a vedere i problemi
- Non è possibile evitare un font flash-of-wrong e mantenere comunque 100/100 PageSpeed. Il primo rendering sarà il carattere sicuro per il Web del client, il secondo render utilizzerà il tipo di carattere che è stato differito in precedenza.
In sostanza ciò che si può fare accadere è:
- client richiede un URL all'interno del tuo sito
- Server invia indietro un file completo HTML con CSS in linea, async Javascript e differita font
- Il client richiede le immagini (se presenti) e il server li invia
- Il client può ora eseguire il rendering della pagina
- font anticipate (se presenti) arrivano e la pagina potrebbe ri-renderizzare
- Javascript nave madre payload arriva nel sfondo
- Meteor stivali e si dispone di una web-app pienamente funzionante con tutte le campane e fischietti e nessuna penalità di primo carico
- Finché si legge dando all'utente alcune righe di testo da leggere e una bella immagine a , non si noterà mai la transizione dalla pagina HTML statica a Web completo - app
Come per raggiungere questo
ho usato Meteor 1.3 e questi pacchetti aggiuntivi:
- reagiscono
- reagire-dom
- reagire-router
- reagire-router-SSR
- react-helmet
- postcss
- autoprefixer
- meteora-node-mozziconi
Reagire giochi piacevoli con il rendering lato server, non ho provato qualsiasi altro motore di rendering. react-helmet è usato per aggiungere e modificare facilmente lo <head>
di ogni pagina sia lato client che lato server (ad esempio per impostare il titolo di ogni pagina). Io uso l'autoprefixer per aggiungere tutti i prefissi specifici del venditore al mio CSS/SASS, certamente non richiesto per questo esercizio.
La maggior parte del sito è quindi abbastanza semplice seguendo gli esempi nel react-router, reac-router-ssr e documentazione del casco di reazione. Vedi i documenti di quei pacchetti per i dettagli su di loro.
Prima di tutto, un file molto importante che dovrebbe trovarsi in una directory Meteor condivisa (cioè non in un server o cartella client). Questo codice configura il rendering lato server React, il tag <head>
, Google Analytics, il monitoraggio di Facebook e scorre fino a ancore #hash.
import { Meteor } from 'meteor/meteor';
import { ReactRouterSSR } from 'meteor/reactrouter:react-router-ssr';
import { Routes } from '../imports/startup/routes.jsx';
import Helmet from 'react-helmet';
ReactRouterSSR.Run(
Routes,
{
props: {
onUpdate() {
hashLinkScroll();
// Notify the page has been changed to Google Analytics
ga('send', 'pageview');
},
htmlHook(html) {
const head = Helmet.rewind();
html = html.replace('<head>', '<head>' + head.title + head.base + head.meta + head.link + head.script);
return html; }
}
},
{
htmlHook(html){
const head = Helmet.rewind();
html = html.replace('<head>', '<head>' + head.title + head.base + head.meta + head.link + head.script);
return html;
},
}
);
if(Meteor.isClient){
// Google Analytics
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-xxxxx-1', 'auto', {'allowLinker': true});
ga('require', 'linker');
ga('linker:autoLink', ['another-domain.com']);
ga('send', 'pageview');
// Facebook tracking
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init', 'xxxx');
fbq('track', "PageView");
fbq('trackCustom', 'LoggedOutPageView');
}
function hashLinkScroll() {
const { hash } = window.location;
if (hash !== '') {
// Push onto callback queue so it runs after the DOM is updated,
// this is required when navigating from a different page so that
// the element is rendered on the page before trying to getElementById.
setTimeout(() => {
$('html, body').animate({
scrollTop: $(hash).offset().top
}, 1000);
}, 100);
}
}
Ecco come sono impostati i percorsi. Notare gli attributi del titolo che vengono successivamente inseriti nel reagire-casco per impostare il contenuto <head>
.
import React from 'react';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import App from '../ui/App.jsx';
import Homepage from '../ui/pages/Homepage.jsx';
import ExamTips from '../ui/pages/ExamTips.jsx';
export const Routes = (
<Route path="/" component={App}>
<IndexRoute
displayTitle="BC Real Estate Math Online Course"
pageTitle="BC Real Estate Math Online Course"
isHomepage
component={Homepage} />
<Route path="exam-preparation-and-tips">
<Route
displayTitle="Top 3 Math Mistakes to Avoid on the UBC Real Estate Exam"
pageTitle="Top 3 Math Mistakes to Avoid on the UBC Real Estate Exam"
path="top-math-mistakes-to-avoid"
component={ExamTips} />
</Route>
);
App.jsx --il componente dell'applicazione esterna. Nota il tag <Helmet>
che imposta alcuni meta tag e il titolo della pagina in base agli attributi del componente di pagina specifico.
import React, { Component } from 'react';
import { Link } from 'react-router';
import Helmet from "react-helmet";
export default class App extends Component {
render() {
return (
<div className="site-wrapper">
<Helmet
title={this.props.children.props.route.pageTitle}
meta={[
{name: 'viewport', content: 'width=device-width, initial-scale=1'},
]}
/>
<nav className="site-nav">...
Una componente esempio pagina:
import React, { Component } from 'react';
import { Link } from 'react-router';
export default class ExamTips extends Component {
render() {
return (
<div className="exam-tips blog-post">
<section className="intro">
<p>
...
Come aggiungere font differite.
Questi caratteri verranno caricati dopo il rendering iniziale e quindi non ritardano il rendering time-to-first. Credo che questo sia l'unico modo per usare i webfonts senza ridurre il punteggio di PageSpeed. Porta comunque a un breve font flash-of-wrong. Mettere questo in un file di script incluso nel client:
WebFontConfig = {
google: { families: [ 'Open+Sans:400,300,300italic,400italic,700:latin' ] }
};
(function() {
var wf = document.createElement('script');
wf.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
wf.type = 'text/javascript';
wf.async = 'true';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(wf, s);
})();
Se si utilizza un servizio eccellente come fontello.com e selezionare manualmente solo le icone che realmente avete bisogno, è possibile incorporare nella vostra linea <head>
CSS e ottenere le icone su prima eseguire il rendering senza attendere un file di font grande.
La Hack
che è quasi sufficiente, ma il problema è che i nostri script, CSS, e font vengono caricate in maniera sincrona e rallentare il rendering e uccidendo il nostro punteggio Page Speed. Sfortunatamente, per quanto posso dire, Meteor 1.3 non supporta ufficialmente alcun modo per allineare il CSS o aggiungere l'attributo async ai tag dello script. Dobbiamo hackerare alcune righe in 3 file del pacchetto coreplate-generator core.
~/.meteor/pacchetti/boilerplate-generatore/.1.0.8.4n62e6 ++ OS + web.browser + web.cordova/OS/boilerplate-generator.js
...
Boilerplate.prototype._generateBoilerplateFromManifestAndSource =
function (manifest, boilerplateSource, options) {
var self = this;
// map to the identity by default
var urlMapper = options.urlMapper || _.identity;
var pathMapper = options.pathMapper || _.identity;
var boilerplateBaseData = {
css: [],
js: [],
head: '',
body: '',
meteorManifest: JSON.stringify(manifest),
jsAsyncAttr: Meteor.isProduction?'async':null, // <------------ !!
};
....
if (item.type === 'css' && item.where === 'client') {
if(Meteor.isProduction){ // <------------ !!
// Get the contents of aggregated and minified CSS files as a string
itemObj.inlineStyles = fs.readFileSync(pathMapper(item.path), "utf8");;
itemObj.inline = true;
}
boilerplateBaseData.css.push(itemObj);
}
...
~ /.meteor/packages/boilerplate-generator/.1.0.8.4n62e6++os+web.browser+web.cordova/os/packages/boilerplate-generator/boilerplate_web.browser.html
<html {{htmlAttributes}}>
<head>
{{#each css}}
{{#if inline}}
<style>{{{inlineStyles}}}</style>
{{else}}
<link rel="stylesheet" type="text/css" class="__meteor-css__" href="{{../bundledJsCssUrlRewriteHook url}}">
{{/if}}
{{/each}}
{{{head}}}
{{{dynamicHead}}}
</head>
<body>
{{{body}}}
{{{dynamicBody}}}
{{#if inlineScriptsAllowed}}
<script type='text/javascript'>__meteor_runtime_config__ = JSON.parse(decodeURIComponent({{meteorRuntimeConfig}}));</script>
{{else}}
<script {{../jsAsyncAttr}} type='text/javascript' src='{{rootUrlPathPrefix}}/meteor_runtime_config.js'></script>
{{/if}}
{{#each js}}
<script {{../jsAsyncAttr}} type="text/javascript" src="{{../bundledJsCssUrlRewriteHook url}}"></script>
{{/each}}
{{#each additionalStaticJs}}
{{#if ../inlineScriptsAllowed}}
<script type='text/javascript'>
{{contents}}
</script>
{{else}}
<script {{../jsAsyncAttr}} type='text/javascript' src='{{rootUrlPathPrefix}}{{pathname}}'></script>
{{/if}}
{{/each}}
</body>
</html>
Ora conta il numero di caratteri in quei 2 file modificati e inseriti i nuovi valori nel campo di lunghezza di voci quei file negli ~/.meteor/pacchetti/boilerplate-generatore/.1.0.8.4n62e6 ++ os + web.browser + web.cordova/os.json
Quindi eliminare la cartella project/.meteor/local per forzare Meteor a utilizzare il nuovo pacchetto core e riavviare l'app (la ricarica non funzionerà). Vedrai solo i cambiamenti nella modalità di produzione.
Questo è ovviamente un hack e si interromperà quando gli aggiornamenti di Meteor. Spero che pubblicando questo e ottenendo un certo interesse, lavoreremo verso un modo migliore.
da fare
cose da migliorare sarebbe:
- Evitare l'hack.Ottenere MDG per supportare ufficialmente lo script asincrono e in linea CSS in modo flessibile
- Lasciare un controllo granulare su cui CSS per linea e che di differire
- consentono il controllo granulare su cui JS per asyn, e che per la sincronizzazione e cui inline .
Una questione rilevante per GitHub Meteor discutere proposte di modifica al testo standard: https://github.com/meteor/meteor/pull/3860 – Noland
Sto anche indagando se questo pacchetto può evitare l'hack: https://atmospherejs.com/meteorhacks/inject-initial – Noland
Hey @Noland - post interessante. Puoi dirmi come gestisci i tuoi dati DDP come negli abbonamenti a react-router-ssr? – TJR