ReactJS, rendering Sisi Server dan beberapa seluk-beluk pemrosesan tag meta halaman

Salah satu masalah yang harus Anda selesaikan saat menulis aplikasi rendering Sisi Server adalah bekerja dengan tag meta yang harus dimiliki setiap halaman, yang membantu mengindeksnya dengan mesin pencari.

Mulai dari google, solusi pertama yang akan Anda tuju adalah kemungkinan besar React Helmet .

Salah satu kelebihan perpustakaan adalah dapat dianggap isomorfis dalam beberapa cara dan dapat digunakan dengan sempurna baik di sisi klien maupun di sisi server.

class Page extends Component { render() { return ( <div> <Helmet> <title>Turbo Todo</title> <meta name="theme-color" content="#008f68" /> </Helmet> {/* ... */} </div> ); } } 

Di server, router akan terlihat seperti ini:

 app.get('/*', (req, res) => { const html = renderToString(<App />); const helmet = Helmet.renderStatic(); res.send(` <!doctype html> <html ${helmet.htmlAttributes.toString()}> <head> ${helmet.title.toString()} ${helmet.meta.toString()} </head> <body ${helmet.bodyAttributes.toString()}> <div id="app">${html}</div> </body> </html> `); }); 

Kedua cuplikan ini sepenuhnya benar dan efisien, tetapi ada satu TETAPI, kode di atas untuk server sepenuhnya sinkron dan karena itu benar-benar aman, tetapi jika menjadi asinkron, ia akan menyembunyikan bug yang sulit di-debug sendiri:

 app.get('/*', async (req, res) => { // .... await anyAsyncAction(); //.... const helmet = Helmet.renderStatic(); // ... }); 

Masalahnya di sini terutama di perpustakaan React Helmet itu sendiri dan, khususnya, di mana ia mengumpulkan semua tag di dalam Pohon React dan menempatkannya pada kenyataannya menjadi variabel global, dan karena kode tersebut telah menjadi tidak sinkron, kode tersebut dapat mencampur permintaan yang diproses secara simultan dari pengguna yang berbeda.

Kabar baiknya di sini adalah bahwa garpu dibuat atas dasar perpustakaan ini dan sekarang lebih baik untuk memberikan preferensi ke perpustakaan bereaksi-helm-async . Paradigma utama di dalamnya adalah bahwa dalam kasus ini, konteks helm reaksi akan diisolasi dalam kerangka permintaan tunggal dengan merangkum aplikasi Pohon React di HelmetProvider:

 import { Helmet, HelmetProvider } from 'react-helmet-async'; app.get('/*', async (req, res) => {​ // ... code may content any async actions const helmetContext = {}; const app = ( <HelmetProvider context={helmetContext}> <App/> </HelmetProvider> ); // ...code may content any async actions const html = renderToString(app); const { helmet } = helmetContext; // ...code may content any async actions }); 

Ini bisa selesai, tetapi mungkin Anda akan melangkah lebih jauh dalam upaya untuk memeras kinerja maksimum dan meningkatkan beberapa metrik SEO. Misalnya, Anda dapat meningkatkan metrik Time To First Byte (TTFB) - ketika server dapat mengirim tata letak halaman dengan potongan saat dihitung, alih-alih menunggu hingga dihitung sepenuhnya. Untuk melakukan ini, Anda akan mulai melihat ke arah menggunakan renderToNodeStream alih-alih renderToString .

Di sini kita dihadapkan dengan masalah kecil. Untuk mendapatkan semua tag meta yang dibutuhkan halaman, kita harus melalui seluruh pohon reaksi aplikasi, tetapi masalahnya adalah bahwa tag meta harus dikirim sebelum saat kita mulai streaming konten menggunakan renderToNodeStream. Bahkan, kita perlu menghitung React Tree dua kali dan terlihat seperti ini:

 app.get('/*', async (req, res) => {​ const helmetContext = {}; let app = ( <HelmetProvider context={helmetContext}> <App/> </HelmetProvider> ); // do a first pass render so that react-helmet-async // can see what meta tags to render ReactDOMServer.renderToString(app); const { helmet } = helmetContext; response.write(` <html> <head> ${helmet.title.toString()}${helmet.meta.toString()} </head> <body> `); const stream = ReactDOMServer.renderToNodeStream(app); stream.pipe(response, { end: false }); stream.on('end', () => response.end('</body></html>')); }); 

Dengan pendekatan seperti itu, kebutuhan untuk optimasi seperti itu pada prinsipnya menjadi pertanyaan besar dan tidak mungkin kita akan meningkatkan metrik TTFB yang ingin kita capai.

Di sini kita bisa memainkan sedikit optimasi dan ada beberapa opsi

  • alih-alih penggunaan renderToString renderToStaticMarkup, yang mungkin akan membantu sampai batas tertentu memenangkan waktu
  • alih-alih menggunakan penyaji yang ditawarkan oleh reaksi dari kotak, buat versi cahaya Anda sendiri dari bagian melalui pohon reaksi, misalnya, berdasarkan perpustakaan reaksi-tree-walker , atau menolak untuk membuat pohon sepenuhnya dan hanya melihat pada tingkat pertama pohon, tidak memperhatikan komponen yang disematkan, jadi katakan rendering dangkal
  • pertimbangkan sistem caching yang kadang-kadang mungkin melewatkan jalan pertama melalui pohon reaksi

Tetapi bagaimanapun juga, segala sesuatu yang digambarkan terdengar terlalu canggih dan, pada prinsipnya, meragukan perlombaan ini untuk efisiensi, ketika beberapa arsitektur kompleks yang abnormal dibangun dalam beberapa milidetik.

Bagi saya, dalam hal ini, bagi mereka yang terbiasa dengan cara mengekstrak data untuk rendering untuk RSK (dan jika seseorang tidak tahu, maka ini adalah artikel yang sangat baik tentang topik ini), kami akan membantu dengan cara yang sama mengekstraksi meta tag untuk halaman.

Konsep umum adalah bahwa kita memiliki file konfigurasi untuk router - ini adalah struktur JS biasa, yang merupakan array objek, masing-masing berisi beberapa bidang tipe komponen , path . Berdasarkan url permintaan, kami menemukan router dan komponen yang terkait dengannya dari file konfigurasi. Untuk komponen-komponen ini, kami mendefinisikan sekumpulan metode statis seperti loadData dan, misalnya, buat Tag Tag untuk meta tag kami.

Dengan demikian, komponen halaman itu sendiri akan menjadi seperti ini:

 class ProductPage extends React.Component { static createMetatags(store, request){ const item = selectItem(store, request.params.product_id); return [] .concat({property: 'og:description', content: item.desc}) .concat({property: 'og:title', content: item.title}) } static loadData(store, request){ // extract external data for SSR and return Promise } // the rest of component } 

Kami telah mendefinisikan metode createMetatags statis yang membuat set meta tag yang diperlukan. Dengan mengingat hal ini, kode pada server akan menjadi seperti ini:

 app.get('/*', async (req, res) => {​​ const store = createStore(); const matchedRoutes = matchRoutes(routes, request.path); // load app state await Promise.all( matchedRoutes.reduce((promises, { route }) => { return route.component.loadData ? promises.concat(route.component.loadData(store, req)) : promises; }, []) ); // to get metatags const metaTags = matchedRoutes.reduce((tags, {route}) => { return route.component.createMetatags ? tags.concat(route.component.createMetatags(store, req)): tags }); res.write(`​ <html>​ <head>​ ${ReactDOMServer.renderToString(() => metaTags.map(tag => <meta {...tag}/>) )}​​ </head>​ <body>​ `);​ const stream = ReactDOMServer.renderToNodeStream(app);​ stream.pipe(response, { end: false });​ stream.on('end', () => response.end('</body></html>'));​ }); 

Yaitu Sekarang kita tidak perlu merender pohon Bereaksi dua kali - kita dapat segera mengekstraksi semua yang kita butuhkan untuk bekerja dari aplikasi isomorfik, dengan analogi dengan ekstraksi data untuk rute.

Source: https://habr.com/ru/post/id484456/


All Articles