Skip to content

Commit

Permalink
Prevent Next.js from removing all of its styles on hydration (#18723)
Browse files Browse the repository at this point in the history
Next.js would try to "recover" if its CSS assets went missing (i.e. a deployment occured) **while the page was initially loading**.

This handled a rare case where we'd try to let the Next.js complete hydrating even though a deployment occured.

However, in practice, this never worked: if the `fetch()` failed, that means the original assets never downloaded themselves (because the `fetch()` should be coming from disk cache).

Instead of letting Next.js get itself into a weird state, let's just stop hydration so that the page doesn't accidentally delete its styles.

The handle-no-styles behavior is already tested in `test/integration/css/test/index.test.js`. There was never a branch for it using its cached styles, so nothing else needs updated.

---

Fixes #17930
  • Loading branch information
Timer authored Nov 3, 2020
1 parent b1503e1 commit 27d7b76
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 22 deletions.
9 changes: 8 additions & 1 deletion packages/next/client/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import * as envConfig from '../next-server/lib/runtime-config'
import type { NEXT_DATA } from '../next-server/lib/utils'
import { getURL, loadGetInitialProps, ST } from '../next-server/lib/utils'
import initHeadManager from './head-manager'
import PageLoader, { looseToArray, StyleSheetTuple } from './page-loader'
import PageLoader, {
INITIAL_CSS_LOAD_ERROR,
looseToArray,
StyleSheetTuple,
} from './page-loader'
import measureWebVitals from './performance-relayer'
import { createRouter, makePublicRouterInstance } from './router'

Expand Down Expand Up @@ -286,6 +290,9 @@ export default async (opts: { webpackHMR?: any } = {}) => {
}
}
} catch (error) {
if (INITIAL_CSS_LOAD_ERROR in error) {
throw error
}
// This catches errors like throwing in the top level of a module
initialErr = error
}
Expand Down
29 changes: 8 additions & 21 deletions packages/next/client/page-loader.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,21 @@
import { ComponentType } from 'react'
import type { ClientSsgManifest } from '../build'
import type { ClientBuildManifest } from '../build/webpack/plugins/build-manifest-plugin'
import mitt from '../next-server/lib/mitt'
import type { MittEmitter } from '../next-server/lib/mitt'
import mitt from '../next-server/lib/mitt'
import {
addBasePath,
markLoadingError,
interpolateAs,
addLocale,
interpolateAs,
markLoadingError,
} from '../next-server/lib/router/router'

import getAssetPathFromRoute from '../next-server/lib/router/utils/get-asset-path-from-route'
import { isDynamicRoute } from '../next-server/lib/router/utils/is-dynamic'
import { parseRelativeUrl } from '../next-server/lib/router/utils/parse-relative-url'

export const looseToArray = <T extends {}>(input: any): T[] =>
[].slice.call(input)

function getInitialStylesheets(): StyleSheetTuple[] {
return looseToArray<CSSStyleSheet>(document.styleSheets)
.filter(
(el: CSSStyleSheet) =>
el.ownerNode &&
(el.ownerNode as Element).tagName === 'LINK' &&
(el.ownerNode as Element).hasAttribute('data-n-p')
)
.map((sheet) => ({
href: (sheet.ownerNode as Element).getAttribute('href')!,
text: looseToArray<CSSRule>(sheet.cssRules)
.map((r) => r.cssText)
.join(''),
}))
}

function hasRel(rel: string, link?: HTMLLinkElement) {
try {
link = document.createElement('link')
Expand All @@ -44,6 +27,8 @@ function pageLoadError(route: string) {
return markLoadingError(new Error(`Error loading ${route}`))
}

export const INITIAL_CSS_LOAD_ERROR = Symbol('INITIAL_CSS_LOAD_ERROR')

const relPrefetch =
hasRel('preload') && !hasRel('prefetch')
? // https://caniuse.com/#feat=link-rel-preload
Expand Down Expand Up @@ -413,7 +398,9 @@ export default class PageLoader {
// should resolve instantly.
Promise.all(cssFiles.map((d) => fetchStyleSheet(d))).catch(
(err) => {
if (isInitialLoad) return getInitialStylesheets()
if (isInitialLoad) {
Object.defineProperty(err, INITIAL_CSS_LOAD_ERROR, {})
}
throw err
}
)
Expand Down

0 comments on commit 27d7b76

Please sign in to comment.