ninetails
2/28/2019 - 1:29 AM

reset

reset

import React, { Fragment, Suspense } from 'react'
import Head from '@ninetails-monorepo-react-ssr/react-kabocha'
import { Link, Route, Switch } from 'react-router-dom'

const UniversalSuspense = global.window ? Suspense : Fragment

const test = {
  read (timeout, cache = global.window) {
    if (this.value) {
      if (cache) {
        return this.value
      }
      const output = this.value

      delete this.value
      delete this.promise

      return output
    }

    if (this.promise) {
      throw this.promise
    }

    this.promise = new Promise(resolve => {
      console.log('settimeout start', Date.now() / 1000)
      setTimeout(() => {
        console.log('settimeout end', Date.now() / 1000)
        this.value = 'foo'
        resolve(this.value)
      }, timeout)
    })

    throw this.promise
  }
}

function LazyTest () {
  const testValue = test.read(2000)
  return <div>{testValue}</div>
}

function Home () {
  return (
    <div>
      <Head>
        <title>Home</title>
      </Head>
      Home
    </div>
  )
}

function About () {
  return (
    <div>
      <Head>
        <title>About</title>
      </Head>
      About
      <UniversalSuspense maxDuration={500} fallback={<div>loading...</div>}>
        <LazyTest />
      </UniversalSuspense>
    </div>
  )
}

const App = () => (
  <div>
    <Head>
      <meta charSet='utf-8' />
      <meta name='viewport' content='width=device-width, initial-scale=1' />
    </Head>
    <nav>
      <ul>
        <li>
          <Link to='/'>Home</Link>
        </li>
        <li>
          <Link to='/about'>About</Link>
        </li>
      </ul>
    </nav>
    <Switch>
      <Route exact path='/' component={Home} />
      <Route path='/about' component={About} />
    </Switch>
  </div>
)

export default App
import React from 'react'
import ReactDOM from 'react-dom'
import { HeadProvider } from '@ninetails-monorepo-react-ssr/react-kabocha'
import { BrowserRouter as Router } from 'react-router-dom'
import getRoot from './client/getRoot'
import App from './App'

const root = getRoot(process.env.REACT_APP_ROOT)

ReactDOM.createRoot(root, { hydrate: root.hasChildNodes() }).render(
  <HeadProvider>
    <Router>
      <App />
    </Router>
  </HeadProvider>
)
import React from 'react'
import { renderToStaticNodeStream, renderToString } from 'react-dom/server'
import {
  HeadProvider,
  createRegistry
} from '@ninetails-monorepo-react-ssr/react-kabocha'
import { StaticRouter as Router } from 'react-router-dom'
import App from './App'

async function renderContent (props) {
  try {
    return renderToString(
      <HeadProvider registry={props.registry}>
        <Router location={props.location} context={props.context}>
          <App />
        </Router>
      </HeadProvider>
    )
  } catch (err) {
    if (err instanceof Promise) {
      await err

      return renderContent(props)
    }

    throw err
  }
}

function serverRenderer ({ clientStats, serverStats }) {
  const { main } = clientStats.assetsByChunkName
  const mainSrc = typeof main === 'string' ? main : main[0]

  return async (req, res, next) => {
    const registry = createRegistry()
    const context = {}

    try {
      const content = await renderContent({
        context,
        location: req.url,
        registry
      })

      if (context.url) {
        return res.redirect(301, context.url)
      }

      res.status(200).write('<!doctype html>')
      renderToStaticNodeStream(
        <html>
          <head>{registry.head()}</head>
          <body>
            <div
              id={process.env.REACT_APP_ROOT || 'root'}
              dangerouslySetInnerHTML={{ __html: content }}
            />
            <script src={`/${mainSrc}`} />
          </body>
        </html>
      ).pipe(
        res,
        { end: 'false' }
      )
    } catch (err) {
      // @todo error page
      console.error(err)
      res.status(500).send('Server error')
    }
  }
}

export default serverRenderer