Universal rendering creates a delay between DOMContentLoaded event to Load event - css

I'm very excited about styled components and would love to use it if it wasn't for this...
I've prepared two example projects using next.js universal rendering library.
The first example is using styled-components as a solution, and the second one is using their default solution for css which is styled-jsx.
Both examples include exactly the same code with a minimum level of complexity.
As you will soon see for yourself - in the styled-components example there is a disturbing delay between DOMContentLoaded event and Load event inwhich the user actually sees the un-styled html markup, while in the second example using styled-jsx this is not the case.
Both demos are hosted online using Zeit now:
1 - https://01-styled-components-sqprkdqeft.now.sh
2 - https://02-styled-jsx-nhrynpsdox.now.sh
Source available on github:
1 - https://github.com/Ajar-Ajar/next-demo--styled-components
2 - https://github.com/Ajar-Ajar/next-demo--styled-jsx
I would very much appreciate any insights regarding why does it happen in one and not the other,
and of course any way to amend this behavior as I would love to use styled-components for its many features and advantages.
Thank you
Ajar
:)

What is missing here is the style injection on the server. Basically, when you write styles in JavaScript you have to get the generated styles on the server and inject them as a style tag into the generated HTML.
The built-in solution for Next does this automatically for you, with styled-components you have to do a tiny bit of manual work and add a pages/_document.js file that looks like this:
import Document, { Head, Main, NextScript } from 'next/document'
import { styleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps ({ renderPage }) {
const page = renderPage()
const styles = (
<style dangerouslySetInnerHTML={{ __html: styleSheet.rules().map(rule => rule.cssText).join('\n') }} />
)
return { ...page, styles }
}
render () {
return (
<html>
<Head>
<title>My page</title>
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}
Notice how we inject a style tag with the styles from styled-components. That's all there is to it, now that flash of unstyled content is gone! 🎉 (this is taken from the official example)
Note: With v2 of styled-components (coming soon, you can get it right now with `npm i --save styled-components#next) there'll be an official API for SSR so it'll look more like this:
import Document, { Head, Main, NextScript } from 'next/document'
import styleSheet from 'styled-components/lib/models/StyleSheet'
export default class MyDocument extends Document {
static async getInitialProps ({ renderPage }) {
const page = renderPage()
const styles = (
<style dangerouslySetInnerHTML={{ __html: styleSheet.getCSS() }} />
)
return { ...page, styles }
}
render () {
return (
<html>
<Head>
<title>My page</title>
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}
Hope that helps!

Here is the recommended way for using styled-components with next to avoid the issue:
https://github.com/vercel/next.js/blob/master/examples/with-styled-components/pages/_document.js
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
render () {
return (
<html>
<Head>
<title>My page</title>
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}

Related

How to change amp url inside head tag in next.js

I want to change the link tag URL ?amp=1 to ?amp inside the head in next.js and as it is auto-generated by next itself.
Also, I tried as per the below code but it's creating a duplication issue.
import Head from 'next/head'
import { useAmp } from 'next/amp'
import { useRouter } from 'next/router'
export default function MetaTag() {
const isAmp = useAmp()
const router = useRouter()
return (
<Head>
<link rel="amphtml" href={`${router?.asPath}?amp`} />
</Head>
)
}
Please refer to the below image to get more ideas:

NextJS - ReactDOMServer does not yet support Suspense

I'm currently trying to incorporate a loader component to a site built with NextJS. I would like to use Suspense to show a loading screen may it be after refreshing the page or changing routes.
This is how my code goes:
import Head from 'next/head'
import { Loader } from '../components/loader'
const { Suspense } = require('React')
function MyApp({ Component, pageProps }) {
return (
<>
<Suspense fallback={<Loader />}>
<Head>
.... some codes such as meta tags, title tags ....
</Head>
<Component {...pageProps} />;
</Suspense>
</>
)
}
My problem is I get an error that says ReactDOMServer does not yet support Suspense. but I would like to use Suspense to enable a loading screen on my page. Much like this website
You can use React 18 features like suspense in Next.js Advanced Features. Obviously it's still experimental and might cause issues with you application.
npm install next#latest react#rc react-dom#rc
To enable, use the experimental flag concurrentFeatures: true
// next.config.js
module.exports = {
experimental: {
concurrentFeatures: true,
},
}
Once enabled, you can use Suspense and SSR streaming for all pages.
import dynamic from 'next/dynamic'
import { lazy, Suspense } from 'react'
import Content from '../components/content'
// These two ways are identical:
const Profile = dynamic(() => import('./profile'), { suspense: true })
const Footer = lazy(() => import('./footer'))
export default function Home() {
return (
<div>
<Suspense fallback={<Spinner />}>
{/* A component that uses Suspense-based */}
<Content />
</Suspense>
<Suspense fallback={<Spinner />}>
<Profile />
</Suspense>
<Suspense fallback={<Spinner />}>
<Footer />
</Suspense>
</div>
)
}
I had a similar issue. I ended up simulating the Suspense with a combination of setState & componentDidMount
render(){
return this.state.browser ? <Component/> : <Placeholder/>
}
componentDidMount(){
this.setState({browser: true})
}
I hope it helps.

How to make Vue3 test utils work with teleport

I have a component that uses teleport to , the test html doesn't seem to be working as expected. I can't find any documentation on this particular use. Here's my test:
describe('MetaHead', () => {
it('dynamic metadata tags contain custom text', () => {
let title = 'My Page';
let description = 'Some description about my page';
// This component uses Vue3's teleport to tag <head>
// we must modify wrapper to contain such tag
document.body.innerHTML = `
<head>
<div id="app"></div>
</head>
`
const wrapper = mount(MetaHead, {
attachTo: document.getElementById('app'),
props: {
title,
description
},
global:{
mocks: {
$route:{fullPath: 'full/path'}
}
}
})
expect(wrapper.html()).toContain(title)
expect(wrapper.html()).toContain(description)
})
})
and the minimal component looks like this:
<template>
<teleport to="head">
<title>{{title}}</title>
<meta property="og:site_name" :content="title">
<meta name="description" :content="description">
</teleport>
</template>
Am I missing something?
the problem here is wrapper.html() only returns HTML in your component - since you are teleporting outside your component, that markup won't show up when you call wrapper.html().
You have a few options. One would be making an assertion against document.body.outerHTML. Another would be using a neat trick with findComponent, I wrote about it here and posted a video about it here.
Another thing you could try that I just thought of (but have not tested) would be:
mount({
template: `
<div id="app" />
<MetaHead />
`,
components: { MetaHead }
})
I don't know if that will work, but worth a try.

NextJS dynamic title

Have been googling forever and found a way to change the <title>. That way is this: https://github.com/zeit/next.js/tree/master/examples/layout-component
The main problem with this is that everytime someone refresh the site/change page the title goes from http://localhost:3000 to the actual Title (eg. About us) and I'm a bit afraid of how this is affecting the SEO.
What is the correct way of chaning the page title dynamically?
My layout file:
import Link from 'next/link'
import Head from './../node_modules/next/head'
export default function Layout({ children, title = 'Welcome to my website' }) {
return (
<div>
<Head>
<title>{title}</title>
</Head>
{children}
</div>
)
}
Check out next-seo and install it in your next.js application.
yarn add next-seo
# or
npm install --save next-seo
And it will handle the page title and the meta description for you magically.
import React from 'react';
import { NextSeo } from 'next-seo'; // then add the `NextSeo` at any `pages/` that you wish
export default () => (
<>
<NextSeo
title="About Us, or just any title that you wish"
description="Then with a short description here."
/>
<p>Simple Usage</p>
</>
);
I have implemented the same tactic on my own web app here.
Well for me this works,
Import <Head> from next,
import Head from 'next/head'
And in return statement,
<>
<Head>
<title>Page Title</title>
</Head>
<section>
<Your_JSX_CODE>
</section>
</>
If you need a dynamic title/description, for example for the route parameters case, you can do this. (Consider that the page name is [id].js)
import React from 'react'
import { NextSeo } from 'next-seo' //as proposed by #xun
// import Head from "next/head" //alternative solution
const Detail = ({id}) => {
const title = `My ${id} title.`
const description = `My ${id} description.`
return (
<>
<NextSeo
title={title}
description={description}
/>
<p>It works!</p>
</>
)}
export default Detail
And at the end of your file:
export async function getServerSideProps({query}) {
const { id } = query
return {
props: {
id
},
};
}
Good luck!
I reninstalled "next" and "next-routes" in my dependencies and now this works.

How to resolve FOUC in React.js

I have built react.js site from create-react-app.
But in production mode, there is FOUC because styles are loaded after html is rendered.
Is there any way to resolve this? I have been searching google for answers, but haven't found proper one yet.
FOUC
FOUC - so called Flash of Unstyled Content can be as very problematic as so many tries of solving this issue.
To the point
Let's consider following configuration of routing (react-router):
...
<PageLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/example' component={Example} />
<Switch>
</PageLayout>
...
where PageLayout is a simple hoc, containing div wrapper with page-layout class and returning it's children.
Now, let's focus on the component rendering based on route. Usually you would use as component prop a React Compoment. But in our case we need to get it dynamically, to apply feature which helps us to avoid FOUC. So our code will look like this:
import asyncRoute from './asyncRoute'
const Home = asyncRoute(() => import('./Home'))
const Example = asyncRoute(() => import('./Example'))
...
<PageLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/example' component={Example} />
<Switch>
</PageLayout>
...
to clarify let's also show how asyncRoute.js module looks like:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Loader from 'components/Loader'
class AsyncImport extends Component {
static propTypes = {
load: PropTypes.func.isRequired,
children: PropTypes.node.isRequired
}
state = {
component: null
}
toggleFoucClass () {
const root = document.getElementById('react-app')
if (root.hasClass('fouc')) {
root.removeClass('fouc')
} else {
root.addClass('fouc')
}
}
componentWillMount () {
this.toggleFoucClass()
}
componentDidMount () {
this.props.load()
.then((component) => {
setTimeout(() => this.toggleFoucClass(), 0)
this.setState(() => ({
component: component.default
}))
})
}
render () {
return this.props.children(this.state.component)
}
}
const asyncRoute = (importFunc) =>
(props) => (
<AsyncImport load={importFunc}>
{(Component) => {
return Component === null
? <Loader loading />
: <Component {...props} />
}}
</AsyncImport>
)
export default asyncRoute
hasClass, addClass, removeClass are polyfills which operates on DOM class attribute.
Loader is a custom component which shows spinner.
Why setTimeout?
Just because we need to remove fouc class in the second tick. Otherwise it would happen in the same as rendering the Component. So it won't work.
As you can see in the AsyncImport component we modify react root container by adding fouc class. So HTML for clarity:
<html lang="en">
<head></head>
<body>
<div id="react-app"></div>
</body>
</html>
and another piece of puzzle:
#react-app.fouc
.page-layout *
visibility: hidden
sass to apply when importing of specific component (ie.: Home, Example) takes place.
Why not display: none?
Because we want to have all components which rely on parent width, height or any other css rule to be properly rendered.
How it works?
The main assumption was to hide all elements until compoment gets ready to show us rendered content. First it fires asyncRoute function which shows us Loader until Component mounts and renders. In the meantime in AsyncImport we switch visibility of content by using a class fouc on react root DOM element. When everything loads, it's time to show everything up, so we remove that class.
Hope that helps!
Thanks to
This article, which idea of dynamic import has been taken (I think) from react-loadable.
Source
https://turkus.github.io/2018/06/06/fouc-react/

Resources