jaredpalmer / after.js

Next.js-like framework for server-rendered React apps built with React Router
https://npm.im/@jaredpalmer/after
MIT License
4.13k stars 201 forks source link

Code Splitting and SSR styles #118

Closed alexandcote closed 6 years ago

alexandcote commented 6 years ago

First of all, you are genius ! Thanks for all your libs ๐Ÿ™

I tested this lib during the weekend and it worked like a charm, I love the approach you propose. I'm wondering If you already had issue with brief flash of unstyled content. I found two other closed issues https://github.com/jaredpalmer/after.js/issues/104 and https://github.com/jaredpalmer/razzle/issues/307 but I think the issue still there with code splitting.

I made some tests and here the result:

Note here that both app run in production mode.

Lazy loaded Home component

2018-03-18 21 34 35

No lazy loaded

2018-03-18 21 33 30

Any idea how can I fix this and keep the code splitting ๐Ÿ’…

Thanks a lot again for your time and work ๐Ÿฅ‡

fredrik-sogaard commented 6 years ago

Can you share how you load your styles? Is is plain css, styled components or something else?

fredrik-sogaard commented 6 years ago

Ah, itโ€™s the example code? You donโ€™t load any other styles?

alexandcote commented 6 years ago

Yeah the example code, no other styles was added. I think the styles are inject in the HTML when the component is loaded.

Thanks for your help ๐Ÿ˜„

jaredpalmer commented 6 years ago

This is a razzle problem i think. However, I don't have a great answer for you. Open to PR's though.

krazyjakee commented 6 years ago

@alexandcote Hmm, is it possible these issues are related? https://github.com/jaredpalmer/after.js/issues/120

On the server, while material-ui does put out class names on elements, it does not output styles in the header. I get the same unstyled to styled flicker when reloading the page.

EDIT: Ignore me, the issue still exists without async component, so it's a material-ui issue.

alexandcote commented 6 years ago

@krazyjakee you can try to remove the lazy loading on the home component.

From

{
    path: "/",
    exact: true,
    component: asyncComponent({
      loader: () => import("./Home"), // required
      Placeholder: () => <div>...LOADING...</div> // this is optional, just returns null by default
    })
  },

To

{
    path: "/",
    exact: true,
    component: Home
}

Let me know if it resolve your problem

krazyjakee commented 6 years ago

@alexandcote sorry, I didn't think to test this first. It did not resolve the problem so it's not the same issue.

garmeeh commented 6 years ago

In After.js here, is this only client side rendered?

If you disable javascript you can see that it renders the Home component contents but only the initial client css that is imported in client.js

garmeeh commented 6 years ago

Tested in Razzle with javascript disabled and page loads with styles as expected so might be an After.js problem?

alexandcote commented 6 years ago

@garmeeh I think it's a problem with asyncComponent. I try to figure how we can fix this but for now I didn't find a solution. If anyone have a idea, let me know โœŒ๏ธ

Madumo commented 6 years ago

@alexandcote This is actually a "problem" with webpack, where css chunks are not handled well when imported from a module that is imported with a dynamic import(). You actually need a plugin to handle those chunks.

Might actually be a good idea if after.js would use this by default. After all, code splitting and SSR is the point here. I'll look into it.

But, you could also just go around that problem and use one of the many css-in-jsโ„ข solution at your disposal!

I highly suggest JSS.

alexandcote commented 6 years ago

I tested with JSS like @Madumo suggest and it worked perfectly ! Thanks again ๐Ÿ˜„ Maybe we should change the example to use JSS @jaredpalmer ?

garmeeh commented 6 years ago

@Madumo makes sense alright. I think this is a Razzle issue rather than after.js since it's building the server etc.

I do think it would be great to have this by default. Currently using extract-css-chunks-webpack-plugin on another project and it means having to use react-universal-component as well. Adding this to Razzle wouldn't be trivial from my understanding on how to implement it.

jaredpalmer commented 6 years ago

This is a Razzle issue. However, with latest alpha's of razzle, we use mini-css-extract. Let's move this issue over there.

AndyArcherKG commented 6 years ago

This still seems to be an issue purely with the lazy loading of a component. As @alexandcote states https://github.com/jaredpalmer/after.js/issues/118#issuecomment-375350142

When you make the component not lazy load you do not get the FOUC. I'm importing a static css file e.g import 'my-stylesheet.css' and this ends up client side rendering instead leading to flash of unstyle content.

I've noticed with the other Razzle examples the CSS file is including on the SSR side, so its definitly the lazy loading side of things associated with After.js asyncComponent()

additionally when the lazy load kicks in the css file is not outputted to the assets.json to be included.

Any ideas?

SheikhG1900 commented 6 years ago

It is actually not related to asyncComponent directly. It is webpack behavior to exlude .css files are inside the .js files which are lazily loaded. When we run npm run build, we can see more then one css file are built. In my case I am loading ./Home file lazily in App.js file.

App.js

import './App.css'
import './tailwind.css'
import React from 'react'
import Route from 'react-router-dom/Route'
import Switch from 'react-router-dom/Switch'
import Loadable from 'react-loadable'

const Home = Loadable({
  loader: () => import('./Home'),
  loading: () => null,
})
const App = () => (
  <Switch>
    <Route exact path="/" component={Home} />
  </Switch>
)

when I run npm run build i got following.

build command result (please note, two .css files are here.) Second chunk build\static\css\1.bundle.ca7057dd.css contains ./Home.css contents.

  44.79 KB  build\static\js\bundle.a50ceeae.js
  6.56 KB   build\static\js\2.17674f1a.chunk.js
  819 B     build\static\css\bundle.cf147a1e.css  --- main  --
  622 B     build\static\js\1.862187fa.chunk.js
  261 B     build\static\css\1.bundle.ca7057dd.css -- with home.css --

assets.json Our assets.json file contains the link of first bundle from above list.

{
  "client": {
    "css": "/static/css/bundle.cf147a1e.css",  // -- main --
    "js": "/static/js/bundle.a50ceeae.js"
  }
}

So flash will be noticed in production mode as second .css is not getting referenced.

Html

<html lang="">
ย  <head>
ย    <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
ย    <meta charSet='utf-8' />
ย    <title>Welcome to Razzle</title>
ย    <meta name="viewport" content="width=device-width, initial-scale=1">
ย    <link rel="stylesheet" href="/static/css/bundle.5c3433c4.css">  <!-- main only -->
ย    </head>
-----------
-----------
</html>

Solution we need to explicitly include that .css. Below is the link which explain how to handle server rendering with react-loadable. https://github.com/jamiebuilds/react-loadable#how-do-i-handle-other-styles-css-or-sourcemaps-map-with-server-side-rendering.

heshamelmasry77 commented 6 years ago

@SheikhG1900 hey man can you please tell me how you got the other styles in the chunks ?

greatwitenorth commented 5 years ago

It seems like this was never resolved. I'm still seeing the flash behavior described by the OP. I'm using a vanilla install of afterjs, ran the production build and served it and I'm still seeing the flash.

SheikhG1900 commented 5 years ago

@SheikhG1900 hey man can you please tell me how you got the other styles in the chunks ?

@heshamelmasry77 please check this code https://github.com/SheikhG1900/with-razzle/blob/73774e85bd28d1a579b9c9f8ca923d689312ed0e/src/server.tsx#L35-L51

swcho commented 5 years ago

It's bit ugly but I got to manage to work it with isomorphic-style-loader.

https://github.com/swcho/razzle-with-afterjs-custom/blob/a19cea7ee334b83da2de02b4a44d8aaf2a0fd24f/src/server.tsx#L22-L70

At least, for style perspective, after.js should provide some way to inject additional header after in render function. But, it would be tricky as style or link tag can be provided after render finished...

krazyjakee commented 5 years ago

We had very strange issues with certain classes not matching up on initial client render.

The fix was to use only react-dom/render in production but for development, only use hydrate after the initial mount.

I may still have a misunderstanding of how everything works but this fixed it for us. We have a very specific setup so this is pseudo code.

client.js

import React from 'react';
import { render, hydrate } from 'react-dom';
import { ensureReady } from '@jaredpalmer/after';
import After from '@jaredpalmer/after/After';
import routes from './routes';

ensureReady(routes).then(data => {
  let renderMethod = render;
  if (window.NODE_ENV === 'development') {
    renderMethod = module.hot ? render : hydrate;
  }

  renderMethod(
    <After data={data} routes={routes} />,
    document.getElementById('root'),
    () => {
      const jssStyles = document.getElementById('jss-ssr');
      if (jssStyles && jssStyles.parentNode) {
        jssStyles.parentNode.removeChild(jssStyles);
      }
    }
  );
});

if (module.hot) {
  module.hot.accept();
}
s123121 commented 5 years ago

From what I research, you may suffer for FOUC because your main css bundle does not include all the necessary styles (because your css is spit among entry). Digging into the code, it is marked as @todo https://github.com/jaredpalmer/razzle/blob/43160eb1cdbbe5b4353a714a3db7faba629a1bd3/packages/razzle/config/createConfig.js#L555

You can combine all css into 1 file inside your razzle.config.js

  config.optimization = {
      splitChunks: {
        cacheGroups: {
          styles: {
            name: 'styles',
            test: /\.css$/,
            chunks: 'all',
            enforce: true,
          },
        },
      },
    }

You also need to change your html accordingly

Asmadeus commented 4 years ago

Solution we need to explicitly include that .css. Below is the link which explain how to handle server rendering with react-loadable. https://github.com/jamiebuilds/react-loadable#how-do-i-handle-other-styles-css-or-sourcemaps-map-with-server-side-rendering.

@SheikhG1900 Is it possible to handle css chunks with asyncComponent from after.js? I tried to do it follow by instructions with react-loadable, but in this case method getInitialProps of component doesn't execute.

nimaa77 commented 4 years ago

it's too late I know, but we fixed this in #232 and it's going to get merged soon.