Cambiar a Next.js y acelerar la carga de la página de inicio de manifold.co 7.5 veces

Hoy estamos publicando una traducción de una historia sobre cómo la transición de React Boilerplate a Next.js , un marco para desarrollar aplicaciones web progresivas basadas en React, ha acelerado la carga de la página de inicio del proyecto manifold.co en 7.5 veces. No se hicieron otros cambios al proyecto, y esta transición, en general, resultó ser completamente invisible para otras partes del sistema. Lo que resultó al final resultó ser incluso mejor de lo esperado.



Resumen de resultados


De hecho, podemos decir que la transición a Next.js nos dio algo así como "un aumento de la productividad del proyecto que surgió de la nada". Así es como se ve el tiempo de carga del proyecto cuando se utilizan varios recursos de hardware y conexiones de red.
Conexión
CPU
A segundos
Después de segundos
% De mejora
Rápido (200 Mbps)
Rápido
1,5
0.2 0.2
750
Mediano (3G)
Rápido
5.6
1.1
500
Mediano (3G)
Medio
7.5
1.3
570
Lento (conexión 3G lenta)
Medio
22
4 4
550

Cuando se usa una conexión rápida y un dispositivo con un procesador rápido, el tiempo de carga del sitio cayó de 1.5 s. hasta 0.2 s., es decir, este indicador mejoró 7.5 veces. En una conexión de calidad media y en un dispositivo con un rendimiento promedio, el tiempo de carga del sitio cayó de 7,5 s. hasta 1.3 s

¿Qué sucede después de que un usuario hace clic en una URL?


Para comprender las características del trabajo de las aplicaciones web progresivas (Progressive Web App, PWA), primero debe comprender lo que sucede entre el momento en que el usuario hace clic en la URL (en la dirección de nuestro sitio web) y el momento en que ve algo en una ventana del navegador (en este caso, nuestra aplicación React).


Etapas de aplicación

Considere las 5 etapas de trabajar con la aplicación, cuyo diagrama se proporciona arriba.

  1. El usuario accede a la URL, el sistema descubre la dirección del servidor utilizando DNS y accede al servidor. Todo esto se hace extremadamente rápido, por lo general toma menos de 100 milisegundos, pero este paso lleva algo de tiempo, por eso se menciona aquí.
  2. Ahora el servidor devuelve el código HTML de la página, pero la página en el navegador permanece vacía hasta que se cargan los recursos necesarios para su visualización (a menos que los recursos se carguen asincrónicamente ). En realidad, se están llevando a cabo más acciones en esta etapa de las que se muestran en el diagrama, pero una revisión conjunta de todos estos procesos también nos conviene.
  3. Después de cargar el código HTML y los recursos más importantes, el navegador comienza a mostrar lo que puede mostrar y continúa cargando todo lo demás (imágenes, por ejemplo) en segundo plano. ¿Te has preguntado alguna vez por qué las imágenes a veces "aparecen" repentinamente en una página, obviamente, más rápido de lo necesario, y otras veces se cargan demasiado? Esto es precisamente por qué sucede esto. Este enfoque le permite crear rápidamente una página terminada.
  4. El código JavaScript se puede analizar y ejecutar solo después de cargarlo. Dependiendo del tamaño del código JS utilizado en la página (y esto puede ser, para una aplicación React típica, bastante grande si el código está empaquetado en un solo archivo), esto puede llevar varios segundos o incluso más (tenga en cuenta que JS el código no necesita, para comenzar a ejecutarse, esperar la carga de todos los demás recursos, a pesar de que en el diagrama se ve exactamente así).
  5. En el caso de una aplicación React, ahora llega el momento en que el código modifica el DOM, lo que hace que el navegador redibuje la página ya mostrada. Entonces comienza otro ciclo de carga de recursos. El tiempo que tome este paso dependerá de la complejidad de la página.

Cuanto más rápido, mejor.


Dado que una aplicación web progresiva toma el código React y produce código estático HTML y CSS, esto significa que el usuario ve la aplicación React ya en el paso 3 del esquema anterior, y no en el paso 5. En nuestras pruebas, esto toma 0.2-4 segundos , que depende de la velocidad de conexión del usuario a Internet y de su dispositivo. Esto es mucho mejor que los 1.5-22 segundos anteriores. Las aplicaciones web progresivas son una forma confiable de entregar aplicaciones React más rápido al usuario.

La razón por la cual las aplicaciones web progresivas y los frameworks relacionados como Next.js aún no son muy populares es porque, tradicionalmente, los frameworks JS no son particularmente exitosos en la generación de código HTML estático. Hoy, todo ha cambiado mucho debido al hecho de que los frameworks como React, Vue y Angular, y otros, tienen un excelente soporte para la representación del lado del servidor. Sin embargo, para utilizar estas herramientas, aún necesita un conocimiento profundo de las características del trabajo de los agrupadores y las herramientas para crear proyectos. Trabajar con todo esto no está exento de problemas.

La reciente aparición de marcos de PWA como Next.js y Gatsby (ambos aparecieron a fines de 2016 - principios de 2017) se ha convertido en un paso serio hacia la adopción generalizada de PWA debido a las barreras de entrada más bajas y al hecho de que el uso de dichos marcos es una tarea simple y agradable.

Aunque no todas las aplicaciones se pueden transferir a Next.js, para muchas aplicaciones React esta transición significa el mismo "rendimiento de la nada" del que estamos hablando aquí, complementado por un uso aún más eficiente de los recursos de la red.

¿Qué tan difícil es migrar a Next.js?


En general, se puede notar que traducir nuestra página de inicio a Next.js no fue muy difícil. Sin embargo, encontramos algunas dificultades causadas por las características de arquitectura de nuestra aplicación.

▍ Rechazar un enrutador React


Tuvimos que abandonar el enrutador React porque Next.js tiene su propio enrutador incorporado, que se combina mejor con optimizaciones con respecto a la separación de código realizada sobre la arquitectura PWA. Esto permite que este enrutador proporcione una carga de página mucho más rápida de lo que esperaría de cualquier enrutador del lado del cliente.

El enrutador Next.js es un enrutador React de alta velocidad, pero todavía no es un enrutador React.

En la práctica, dado que no aprovechamos las características particularmente avanzadas que ofrece el enrutador React, la transición al enrutador Next.js para nosotros fue simplemente reemplazar el componente enrutador React estándar con el componente Next.js correspondiente:

/*   ( React) */ <Link to="/my/page">  A link </Link> /*   ( Next.js) */ <Link href="/my/page" passHref>  <a>    A link  </a> </Link> 

En general, todo resultó no ser tan malo. Tuvimos que cambiar el nombre de la propiedad y agregar una etiqueta para fines de representación del servidor. Como también utilizamos la biblioteca de styled-components , resultó que en la mayoría de los casos necesitábamos agregar la propiedad passHref para garantizar que el sistema se comporta de tal manera que href siempre apunte a la etiqueta generada.


Solicitudes de red para manifold.co

Para ver con sus propios ojos la optimización del enrutador Next.js en acción, abra la pestaña Red de las herramientas de desarrollo del navegador al ver la página manifold.co y haga clic en algún enlace. La figura anterior muestra el resultado de hacer clic en el enlace /services . Como puede ver, conduce a la ejecución de la solicitud para cargar services.js lugar de la solicitud habitual.

No estoy hablando solo del enrutamiento del lado del cliente; el enrutador React también es adecuado para resolver este problema. Estoy hablando de una pieza real de código JavaScript que se extrajo del resto del código y se cargó a pedido. Esto se hace usando el estándar Next.js. Y esto es mucho mejor que lo que teníamos antes. Es decir, estamos hablando de un gran paquete de código JS con un tamaño de 1.7 MB, que el cliente, antes de que pudiera ver algo, tuvo que descargar y procesar.

Aunque la solución presentada aquí no es perfecta, está mucho más cerca que la anterior a la idea de que los usuarios solo descargan código para las páginas que ven.

▍ Características del uso de Redux


Continuando con el tema de las dificultades asociadas con la transición a Next.js, se puede observar que todas las optimizaciones interesantes que Next.js experimenta la aplicación tienen un cierto impacto en esta aplicación. Es decir, dado que Next.js realiza la separación del código a nivel de página, evita que el desarrollador acceda al componente raíz React o al método render() de la biblioteca react-dom . Si ya ha estado involucrado en la configuración de Redux, entonces puede notar que todo esto nos dice que para la operación normal con Redux necesitamos resolver el problema, que es que no está claro exactamente dónde buscar Redux.

Next.js proporciona un componente especial de orden superior, con withRedux , que withRedux como un contenedor para todos los componentes de nivel superior en cada página:

 export default withRedux(HomePage); 

Aunque todo esto no es tan malo, pero si necesita métodos createStore() , como cuando usa inyectores reductor-reductor , espere que necesitará tiempo adicional para depurar el envoltorio (y por cierto, nunca intente use algo como redux-reducer-injectors ).

Además, debido al hecho de que Redux ahora es una "caja negra", el uso de la biblioteca Inmutable se vuelve problemático. Aunque el hecho de que Immutable funcione con Redux parece bastante obvio, me encontré con un problema. Entonces, o el estado de nivel superior no era inmutable (el get is not a function error de get is not a function ), o el componente contenedor intentó usar la notación de puntos para trabajar con objetos JS en lugar del método .get() ( Can't get catalog of undefined errores Can't get catalog of undefined ). Para depurar este problema, tuve que referirme al código fuente. Después de todo, Next.js obliga al desarrollador a usar sus propios mecanismos por una razón.

En general, se puede observar que el principal problema asociado con Next.js es que muy poco en este marco está bien documentado. Hay muchos ejemplos en la documentación sobre la base de los cuales puede crear algo propio, pero si entre ellos no hay uno que refleje las características de su proyecto, solo puede desear buena suerte.

▍Fetch rechazo


Utilizamos la biblioteca react-inlinesvg , que ofrece opciones de estilo para imágenes SVG incrustadas y almacenamiento en caché de consultas. Pero aquí tuvimos un problema: al realizar la representación del servidor, no existen las solicitudes XHR (al menos no en el sentido de las URL generadas por Webpack, como es de esperar). Los intentos de ejecutar tales solicitudes interfieren con la representación del servidor.

Aunque hay otras bibliotecas para trabajar con datos SVG incrustados que admiten SSR, decidí abandonar esta función, ya que los archivos SVG todavía rara vez se usaban. Los reemplacé con imágenes normales, etiquetas <img> , si no necesitaba estilo al mostrar las imágenes correspondientes, o las inserté en el código en forma de React JSX. Probablemente, todo mejoró, ya que las ilustraciones de JSX ahora llegaron al navegador cuando se cargó la página por primera vez y el paquete JS enviado al cliente tenía 1 biblioteca menos.

Si necesita usar mecanismos de carga de datos (necesitaba esta característica para otra biblioteca), puede configurarlo con next.config.js , usando whatwg-fetch y node-fetch :

 module.exports = { webpack: (config, options) =>   Object.assign(config, {     plugins: config.plugins.concat([       new webpack.ProvidePlugin(         config.isServer           ? {}           : { fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch' }       ),     ]),   resolve: Object.assign(config.resolve, {     alias: Object.assign(       config.resolve.alias,       config.isServer ? {} : { fetch: 'node-fetch' }     ),   }), }), }; 

▍ Cliente y servidor JS


La última característica de Next.js, que me gustaría mencionar aquí, es que este marco se inicia dos veces, una para el servidor y otra para el cliente. Esto difumina ligeramente la línea entre el código JavaScript del lado del cliente y el código Node.js en la misma base de código, lo que provoca errores inusuales como fs is undefined cuando se intenta aprovechar las características de Node.js en el cliente.

Como resultado, tenemos que construir tales construcciones en next.js.config :

 module.exports = { webpack: (config, options) =>   Object.assign(config, {     node: config.isServer ? undefined : { fs: 'empty' },   }), }; 

El indicador config.isServer en Webpack será su mejor amigo si necesita ejecutar el mismo código en diferentes entornos.

Además, Next.js admite, además de los métodos estándar para el ciclo de vida de los componentes React, el método getInitialProps() , que se llama solo cuando el código se getInitialProps() en modo servidor:

 class HomePage extends React.Component { static getInitialProps() {   //         } componentDidMount() {   //     ,    } … } 

Sí, y no olvidemos que nuestro buen amigo, el objeto de window , necesario para organizar la escucha de eventos, para determinar el tamaño de la ventana del navegador y dar acceso a muchas funciones útiles, no está disponible en Node.js:

 if (typeof window !== 'undefined') { // ,     `window`      } 

Cabe señalar que incluso Next.js no puede salvar al desarrollador de la necesidad de resolver problemas asociados con la ejecución del mismo código en el servidor y en el cliente. Pero al resolver tales problemas, config.isServer y getInitialProps() son muy útiles.

Resultados: ¿qué pasará después de Next.js?


A corto plazo, el marco Next.js coincide perfectamente, en términos de rendimiento, con nuestros requisitos para la representación del servidor y la capacidad de ver nuestro sitio en dispositivos que tienen JavaScript deshabilitado. Además, ahora le permite usar metaetiquetas avanzadas (ricas).

Quizás en el futuro consideremos otras opciones en caso de que nuestra aplicación necesite tanto la representación del servidor como una lógica de servidor más compleja (por ejemplo, consideramos la posibilidad de implementar tecnología de inicio de sesión único en manifold.co y dashboard.manifold.co ) Pero hasta entonces usaremos Next.js, ya que este marco, con pequeños costos de tiempo, nos trajo enormes beneficios.

Estimados lectores! ¿Usas Next.js en tus proyectos?

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


All Articles