Det bästa av två världar: SSR med isomorf JavaScript

Server-side rendering, eller SSR, är en fras som du ofta hör i frontend development community.

på den mest grundläggande nivån är serverns rendering exakt vad den beskriver: rendering av applikationer på servern. Du navigerar till en webbplats, det gör en begäran till servern, det gör vissa HTML, och du får helt renderade resultatet tillbaka i din webbläsare. Ganska enkelt. Du kanske frågar dig själv varför samhället till och med har ett buzzword för detta.

innan uppkomsten av rika och dynamiska webbapplikationer som förlitade sig starkt på JavaScript och jQuery, var i huvudsak alla webbapplikationer serverrenderade. PHP, WordPress och till och med bara grundläggande HTML-webbplatser är exempel på detta.

när du besöker en sida på en av dessa platser, du får tillbaka alla HTML — data och alla. Om du klickar på en länk kommer webbläsaren att göra en annan begäran till servern. Efter svaret uppdateras webbläsaren och återger Nästa sida från grunden. Detta tillvägagångssätt fungerar bra, och det har i flera år; webbläsare är spektakulärt snabba på att göra statisk HTML. Vad har förändrats?

sedan sekelskiftet har JavaScript-användningen gått från lite strö här och där för webbsideinteraktivitet till det obestridda språket som valts på webben. Vi levererar ständigt mer logik och JavaScript till webbläsaren.

enkelsidiga ramar som React och Vue har inlett denna nya era av dynamiska, komplexa, datadrivna klientrenderade webbapplikationer. Dessa Spa skiljer sig från serverrenderade applikationer eftersom de inte hämtar fullständigt renderat innehåll komplett med data från servern innan de återges på skärmen.

klient-sida-renderade applikationer gör sitt innehåll i webbläsaren med JavaScript. I stället för att hämta allt innehåll från servern hämtar de helt enkelt en barebones HTML-sida utan kroppsinnehåll och gör allt innehåll inuti med JavaScript.

fördelen med detta är att du undviker hela sidan uppdateras som händer med fullt server renderade program, som kan vara lite skärande för användaren. Enkelsidiga klientrenderade appar kommer att uppdatera innehåll på din skärm, hämta data från API: er och uppdatera precis framför dig utan någon form av siduppdatering alls. Denna egenskap är det som gör att moderna webbapplikationer känns snygga och” inbyggda ” när du interagerar med dem.

klientsidan rendering avvägningar

det är inte allt solsken och regnbågar i klientsidan renderade SPA världen. Det finns några avvägningar som kommer med att göra din ansökan på klientsidan. Två primära exempel är SEO och initial load performance.

SEO

eftersom klientrenderade appar returnerar en HTML-sida med bara ben med mycket lite innehåll innan JavaScript sparkar in och gör resten, kan det vara svårt för sökmotorsökare att förstå HTML-strukturen på din sida, vilket är skadligt för sökrankningen på din webbplats. Google har gjort mycket bra arbete kring detta, men det rekommenderas fortfarande att undvika rendering på klientsidan om SEO är särskilt viktigt.

Initial belastningsprestanda

med klientrenderade appar ser du vanligtvis följande saker hända när du först öppnar sidan:

  • appen laddar några grundläggande HTML, som en app skal eller statisk navbar
  • du ser en laddningsindikator av något slag
  • ditt innehåll återges sedan

problemet med detta är att din ansökan inte kommer att visa något förrän JavaScript laddas helt från nätverket och är klar rendering element på skärmen.

i ett nötskal är problemet med klientsidans prestanda i allmänhet att du inte kan kontrollera vilken klientenhet någon använder din applikation på — oavsett om det är deras toppmoderna smartphone, kraftfull avancerad stationär maskin eller $100 lägre smartphone.

vi kontrollerar dock servern. Vi kan nästan alltid ge vår server mer CPU och minne och justera det för att få det att fungera bättre för våra användare.

det bästa av två världar

vi kan ha det bästa av två världar när du använder serversidan rendering med modern frontend teknik. Det sätt som detta i allmänhet fungerar är att servern gör och skickar tillbaka den fullständigt renderade applikationen vid första belastningen. Nästa steg, känt som hydrering, är där ditt JavaScript-paket kommer att laddas ner och köras. Detta fäster händelsehanterare och ledningar upp saker som din klientsidan router.

med detta tillvägagångssätt får du alla fördelar med SSR på den ursprungliga belastningen, då kommer varje interaktion från den punkten framåt att hanteras av JavaScript på klientsidan. Detta ger en snabb, SEO-vänlig initial belastning, följt av den dynamiska webbappupplevelsen på en sida som vi känner och älskar.

applikationer som detta kallas universella applikationer eftersom samma JavaScript körs på klienten och servern. Du kan också höra den snyggare termen ”isomorf” som används, vilket betyder exakt samma sak.

handledning: implementering av SSR

SSR är inte heller utan avvägningar. Det lägger overhead till din utveckling genom att införa en mer komplex konfiguration samt att behöva vara värd och hantera din egen server. Dessa problem är varför otroliga ramar som nästa.js och Razzle är mycket populära: de abstraherar bort SSR-konfigurationsdelen och låter dig fokusera på att skriva UI-kod.

i denna handledning kommer vi inte att använda några SSR-ramar. Det bästa sättet att lära sig hur något fungerar är att faktiskt bygga det, så vi kommer att lära oss hur man skapar den enklaste SSR-inställningen som vi eventuellt kan ge:

  • Global CDN
  • fullt fungerande backend API
  • inga servrar eller infrastruktur för att hantera
  • single-command deployment

vi kommer att distribuera en universell server-renderade React program som skapats med create-react-app på Amazon Web Services (AWS). Du behöver inte ha erfarenhet av AWS för att följa med.

våra verktyg

för att bygga vår applikation kommer vi att använda några olika AWS-tjänster.

  • AWS Amplify: Ett ramverk på hög nivå för hantering av AWS-tjänster, främst för mobil-och webbutveckling
  • AWS Lambda: kör kod i molnet utan att hantera servrar
  • AWS Cloudfront (CDN): ett innehållsleveransnätverk som ansvarar för att leverera och cacha innehåll över hela världen
  • AWS Simple Storage Service (S3): där vi lagrar våra statiska tillgångar (JS, CSS, etc.)

Arkitekturdiagram

vår Lambda-funktion är ansvarig för server-rendering av vår React-applikation. Vi kommer att använda S3 för att lagra vårt statiska innehåll och Cloudfront CDN för att betjäna det. Du behöver inte ha några förkunskaper om dessa tjänster, eftersom AWS Amplify kommer att göra det super enkelt för oss att skapa dem.

 vårt Arkitekturdiagram

vårt Arkitekturdiagram

bygga vår applikation

först och främst måste du installera AWS Amplify CLI och skapa ett AWS-konto om du inte redan har ett. Du kan göra det genom att följa den här korta guiden.

Projektinställning

nu när Amplify är konfigurerat kan vi börja ställa in vårt React-projekt. Vi kommer att använda den fantastiska create-react-appen för att hjälpa oss. Förutsatt att du har nod.js och npm installerat, vi kan köra:

npx create-react-app amplify-ssrcd amplify-ssr yarn add aws-amplify amplify init

Välj standardalternativen i guiden AWS Amplify.

vårt React-projekt är nu bootstrapped med Amplify och redo för oss att lägga till vår ”server” för SSR. Vi gör detta genom att köra amplify add api och svara på några frågor:

$ amplify add api? Please select from one of the below mentioned services: REST? Provide a friendly name for your resource to be used as a label for this category in the project: amplifyssr? Provide a path (e.g., /items): /ssr? Choose a Lambda source: Create a new Lambda function? Provide a friendly name for your resource to be used as a label for this category in the project: amplifyssr? Provide the AWS Lambda function name: ssr? Choose the function runtime that you want to use: NodeJS? Choose the function template that you want to use: Serverless expressJS function? Do you want to access other resources created in this project from your Lambda function? N? Do you want to edit the local lambda function now? N? Restrict API access: N? Do you want to add another path? N

detta skapar relevanta mallar, kataloger och kod som behövs för vår AWS-infrastruktur och backend: en AWS Lambda-funktion som kör en liten Express-server som ansvarar för att göra vår React-applikation.

innan vi distribuerar vår infrastruktur finns det några ändringar vi behöver göra i vår React-applikation för att förbereda den för rendering på serversidan. Öppna src/App.js (huvudappkomponenten för din React-applikation) och klistra in följande:

import React from 'react';function App() { return ( <div className="App"> <header className="App-header"> Server Rendered React App </header> </div> );}export default App;

Därefter måste vi skapa ett skript som kommer att göra vår React-applikation på serversidan. Detta görs med funktionen renderToString i paketet react-dom/server. Den här funktionen är ansvarig för att ta vår <App /> – komponent och göra den på serversidan som en sträng, redo att returneras som fullständigt renderad HTML till klienten.

skapa en fil på src/render.js med följande kod:

import React from "react";import { renderToString } from "react-dom/server";import App from "./App";export default () => renderToString(<App />);

Great – vår klientsidan React app har all kod som behövs för att återges på serversidan. Det betyder att vi nu måste koda upp serverns slutpunkt som kommer att göra vår React-applikation.

vi har dock ett problem — vi behöver funktionen src/render och vår <App /> komponentkod som ska köras på serversidan. Servern vet ingenting om React eller till och med ES-moduler som standard. Av denna anledning kommer vi att transpilera koden från React-applikationen med Babel till serversidan.

för att göra detta, låt oss installera några Babel-beroenden i vårt projekt.

yarn add --dev @babel/core @babel/cli @babel/preset-react @babel/preset-env

skapa sedan en .babelrc i roten till ditt projekt. Den här filen används för att konfigurera Babel och berätta vilka plugins/förinställningar som ska användas.

{ "presets":}

slutligen, låt oss uppdatera vår package.json för att transpilera vår kod som en del av byggsteget. Detta kommer att överföra filerna till katalogen amplify/backend/function/amplifyssr/src/client, där vi lagrar allt universellt JavaScript som måste köras på klientsidan såväl som servern för SSR.

 "scripts": { "start": "react-scripts start", "transpile": "babel src --out-dir amplify/backend/function/amplifyssr/src/client --copy-files", "build": "npm run transpile && react-scripts build && npm run copy", "copy": "cp build/index.html amplify/backend/function/amplifyssr/src/client", "test": "react-scripts test", "eject": "react-scripts eject" },

gör appen i Lambda

byggkonfigurationen är klar! Låt oss hoppa in i amplify/backend/function/amplifyssr/src och installera react och react-dom, eftersom de båda kommer att krävas för att Lambda ska utföra SSR.

yarn add react react-dom

nu för att konfigurera vår Express-server, som kommer att köras på Lambda. Lambda – funktionen genererades automatiskt för oss när vi slutförde amplify add api – steget tidigare och valde ett REST och ExpressJS API.

Amplify har redan konfigurerat Express-servern för att vi ska kunna köra på Lambda, så allt vi behöver göra nu är att lägga till en slutpunkt till servern-gör vår React-applikation när någon träffar API-URL i webbläsaren. Uppdatera din amplify/backend/function/amplifyssr/src/app.js – fil så att den innehåller följande kod:

/* Amplify Params - DO NOT EDIT ENV REGIONAmplify Params - DO NOT EDIT */const express = require('express')const bodyParser = require('body-parser')const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware')const fs = require('fs');const render = require('./client/render').default;// declare a new express appconst app = express()app.use(bodyParser.json())app.use(awsServerlessExpressMiddleware.eventContext())// Enable CORS for all methodsapp.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*") res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") next()});app.get('*', function(req, res) { // Read the index.html file from the create-react-app build const html = fs.readFileSync("./client/index.html", "utf-8"); // Server side render the react application const markup = render(); // Replace the empty body of index.html with the fully server rendered react application and send it back to the client res.send(html.replace(`<div></div>`, `<div>${markup}</div>`))});module.exports = app

vår Express-server är nu SSR-klar, och vi kan distribuera vår React-applikation.

Hosting och sista handen

när vi får server renderade HTML tillbaka från vår app första render, kommer vi sedan hämta klientsidan JavaScript bunt att ta över därifrån och ge oss en helt interaktiv SPA.

vi behöver någonstans att vara värd för våra klientsidan JavaScript och statiska filer. I AWS är den tjänst som vanligtvis används för detta S3 (Simple Storage Service), en massivt skalbar molnobjektbutik.

vi kommer också att sätta en CDN framför den för global caching och prestanda. Med Amplify kan vi skapa båda dessa resurser för vårt projekt genom att köra några kommandon från vår projektrotkatalog:

$ amplify add hostingSelect the plugin module to execute Amazon CloudFront and S3? Select the environment setup: PROD (S3 with CloudFront using HTTPS)? hosting bucket name (name your bucket or use the default)

du kan nu distribuera hela din infrastruktur, inklusive din Express server Lambda-funktion, S3-hink och CDN, genom att köra kommandot amplify publish.

din konsolutgång visar alla relevanta resurser från dina mallar som skapas för dig av Amplify. Observera att det kan ta ett tag att skapa en Cloudfront CDN, så ha tålamod. När dina resurser har skapats visas din Cloudfront CDN-URL i terminalen.

Publish started for S3AndCloudFront✔ Uploaded files successfully.Your app is published successfully.https://d3gdcgc9a6lz30.cloudfront.net

det sista vi behöver göra är att berätta för React varifrån vi ska hämta vårt paket på klientsidan efter att appen har gjorts av servern. Detta görs i create-react-app med miljövariabeln PUBLIC_URL. Låt oss uppdatera vår React app package.json skript igen för att se ut som följande:

 "scripts": { "start": "react-scripts start", "transpile": "babel src --out-dir amplify/backend/function/amplifyssr/src/client --copy-files", "build": "npm run transpile && PUBLIC_URL=<your-cloudfront-url> react-scripts build && npm run copy", "copy": "cp build/index.html amplify/backend/function/amplifyssr/src/client", "test": "react-scripts test", "eject": "react-scripts eject" },

bygga om och distribuera din ansökan till AWS med denna uppdaterade konfiguration.

amplify publish

vi borde nu ha en helt server-sida-renderad React-app som körs på AWS!

kör vår app

din SSR API-URL finns på amplify/backend/amplify-meta.json. Leta efter RootUrl i din JSON-fil och du bör se webbadressen där du kan besöka din nya serverrenderade applikation. Det ska se ut som följande:

"output": { "ApiName": "amplifyssr", "RootUrl": "https://g6nfj3bvsg.execute-api.eu-west-1.amazonaws.com/dev", "ApiId": "g6nfj3bvsg"}, 

besök API Gateway URL i din webbläsare på <your-api-url>/ssr och du bör se din skinande nya server renderade React ansökan! Om du dyker in på Nätverksfliken i din webbläsare och tittar på förfrågningarna kommer du att märka att begäran till /ssr har ett fullständigt renderat HTML-svar med vår React-applikation som återges i <body> i dokumentet.

<div> <div class="App" data-reactroot=""> <header class="App-header">Server Rendered React App</header> </div></div>

du kommer också att märka de förfrågningar som görs till din Cloudfront URL från webbläsaren för att ladda klientsidan JavaScript som tar över rendering härifrån, vilket ger oss det bästa av både klientsidan och serversidan rendering världar.

vart ska man gå härifrån

denna handledning är avsedd att få dig igång med serverns rendering så snabbt som möjligt utan att oroa dig för att hantera infrastruktur, CDN och mer. Efter att ha använt den serverlösa metoden finns det några fina förbättringar vi kan göra i vår installation.

Provisionerad samtidighet

ett sätt att AWS Lambda kan förbli extremt låg kostnad är att Lambda-funktioner som inte har drabbats på ett tag kommer att gå ”tomgång.”Det betyder i huvudsak att när vi kör dem igen kommer det att bli det som kallas en ”kallstart” — en initialiseringsfördröjning som måste hända innan Lambda svarar.

efter detta är lambda sedan” varm ” igen under en tidsperiod och kommer att svara på efterföljande förfrågningar snabbt fram till nästa långa tomgångsperiod. Detta kan orsaka lite opålitliga svarstider.

trots att den är ”serverlös” använder Lambda lätta behållare för att behandla alla förfrågningar. Varje container kan behandla endast en begäran vid varje given tidpunkt. Förutom kallstartproblemet efter en tomgångsperiod gäller detsamma också när många samtidiga förfrågningar träffar samma Lambda-funktion, vilket gör att fler samtidiga behållare eller arbetare kallstartas innan de svarar.

tidigare har många ingenjörer löst problemet genom att skriva skript för att pinga Lambda regelbundet för att hålla det varmt. Det finns nu ett mycket bättre AWS-native sätt att lösa detta, och det är känt som Provisioned Concurrency.

med tillhandahållen samtidighet kan du mycket enkelt begära ett visst antal dedikerade behållare för att hålla dig varm för en specifik Lambda-funktion. Detta ger dig en mycket mer konsekvent SSR-svarstid i tider med hög och sporadisk belastning.

Lambda-versioner

du kan skapa flera Lambda-versioner för dina funktioner och dela trafik mellan dem. Detta är mycket kraftfullt i vår SSR-applikation eftersom det gör att vi kan göra uppdateringar på Lambda-sidan och A/B testa dem med en mindre del av användarna.

du kan publicera flera versioner av din Lambda och dela trafik mellan dem i vikter som du anger. Du kanske till exempel vill serverrendera en CTA-banner för vissa användare för att mäta engagemang, men inte göra det för andra. Du kan göra detta med Lambda-versioner.

Full-stack webbapplikation

som förklarats tidigare skapar AWS Amplify redan ett REST API och en Express-server för oss, där vi har skapat en slutpunkt för server-render vår React-applikation. Vi kan alltid lägga till mer kod och slutpunkter till denna Express-server på amplify/backend/function/amplifyssr/src/app.js, så att vi kan förvandla vår app till en full-stack webbapplikation, komplett med databas, autentisering och mer.

du kan använda den fantastiska sviten av AWS Amplify — verktyg för att skapa dessa resurser eller ansluta till din egen infrastruktur-även om den inte är värd på AWS. Du kan behandla din AWS Lambda backend som någon annan Express-server och bygga ovanpå den.

du har redan hela Distributions pipeline inrättas genom att köra amplify publish så att du kan fokusera på att skriva kod. Utgångspunkten i denna handledning ger dig total flexibilitet att göra vad du vill härifrån.

slutsats

rendering på serversidan behöver inte vara svårt. Vi kan använda helt hanterade verktyg som Next eller Razzle, som är fantastiska i sig, men för många lag kan detta vara alltför stort ett paradigmskifte med tanke på deras befintliga kod eller krav. Att använda ett enkelt, lågt underhåll, anpassat tillvägagångssätt kan göra livet enklare, särskilt om du redan använder AWS eller Amplify för ditt projekt.

SSR kan lägga till massor av värde för dina webbapplikationer och ge en välbehövlig prestanda eller SEO-boost. Vi är lyckliga i webbutvecklingsgemenskapen att ha verktyg som kan skapa CDN, serverlösa backends och fullt värd webbapplikationer med några kommandon eller klick.

även om du inte tror att du behöver SSR är det ett mycket utbrett och vanligt ämne i JavaScript-ekosystemet. Att ha en förståelse för dess fördelar och avvägningar kommer att vara till nytta för nästan alla som är involverade i webbutvecklingsområdet.

jag hoppas att du lärde dig något idag-tack för att du läste! Känn dig fri att nå ut till mig eller följ mig på Twitter, där jag tweetar och bloggar om JavaScript, Python, AWS, automation och no-code development.

Full insyn i production React apps

felsökning av React-applikationer kan vara svårt, särskilt när användare upplever problem som är svåra att reproducera. Om du är intresserad av att övervaka och spåra Redux state, automatiskt ytbehandling JavaScript fel, och spåra långsamma nätverksförfrågningar och komponentladdningstid, prova LogRocket. LogRocket Dashboard Free Trial Banner

LogRocket är som en DVR för webbappar, inspelning bokstavligen allt som händer på din React app. Istället för att gissa varför problem uppstår kan du aggregera och rapportera om vilket tillstånd din ansökan var i när ett problem inträffade. LogRocket övervakar också appens prestanda, rapporterar med mätvärden som klient CPU-belastning, klientminneanvändning och mer.

LogRocket Redux middleware-paketet lägger till ett extra lager av synlighet i dina användarsessioner. LogRocket loggar alla åtgärder och tillstånd från dina Redux butiker.

modernisera hur du felsöker dina React-appar-börja övervaka gratis.

Lämna ett svar

Din e-postadress kommer inte publiceras.