我在理解Next.js将Firebase与serverless app结合使用的示例时遇到麻烦。
我已经成功地实现了身份验证阶段,并将Google添加为身份验证提供者。我遇到的问题是,登录后立即加载索引页面时,服务器似乎无权访问Next团队创建的共享AuthUser
和AuthUserInfo
对象验证信息。
但是,如果刷新页面,则该信息可用。这让我感到困惑。
在下面,我分享了index.js
和AuthUser
和AuthUserInfo
组件的代码。
import React from 'react'
import PropTypes from 'prop-types'
import { get } from 'lodash/object'
import Link from 'next/link'
import Router from 'next/router'
import withAuthUser from '../utils/pageWrappers/withAuthUser'
import withAuthUserInfo from '../utils/pageWrappers/withAuthUserInfo'
import logout from '../utils/auth/logout'
const Index = props => {
const { AuthUserInfo, data } = props
const AuthUser = get(AuthUserInfo, 'AuthUser', null)
const { favoriteFood } = data
// [OP's COMMENT]: This logs the AuthUser object correctly even immediately after the login
console.log(AuthUser)
return (
<div>
<p>Hi there!</p>
{!AuthUser ? (
<p>
You are not signed in.{' '}
<Link href={'/auth'}>
<a>Sign in</a>
</Link>
</p>
) : (
<div>
<p>You're signed in. Email: {AuthUser.email}</p>
<p
style={{
display: 'inlinelock',
color: 'blue',
textDecoration: 'underline',
cursor: 'pointer',
}}
onClick={async () => {
try {
await logout()
Router.push('/auth')
} catch (e) {
console.error(e)
}
}}
>
Log out
</p>
</div>
)}
<div>
<Link href={'/example'}>
<a>Another example page</a>
</Link>
</div>
<div>
<div>Your favorite food is {favoriteFood}.</div>
</div>
</div>
)
}
// Just an example.
const mockFetchData = async userId => ({
user: {
...(userId && {
id: userId,
}),
},
favoriteFood: 'pizza',
})
Index.getInitialProps = async ctx => {
// Get the AuthUserInfo object. This is set in `withAuthUser.js`.
// The AuthUserInfo object is available on both the server and client.
const AuthUserInfo = get(ctx, 'myCustomData.AuthUserInfo', null)
const AuthUser = get(AuthUserInfo, 'AuthUser', null)
// You can also get the token (e.g., to authorize a request when fetching data)
const AuthUserToken = get(AuthUserInfo, 'token', null)
// [OP's COMMENT]: In the initial login this returns null, but after refreshing while logged in it returns the infomation correctly correctly
console.log(AuthUser)
// You can fetch data here.
const data = await mockFetchData(get(AuthUser, 'id'))
return {
data,
}
}
Index.displayName = 'Index'
Index.propTypes = {
AuthUserInfo: PropTypes.shape({
AuthUser: PropTypes.shape({
id: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
emailVerified: PropTypes.bool.isRequired,
}),
token: PropTypes.string,
}),
data: PropTypes.shape({
user: PropTypes.shape({
id: PropTypes.string,
}).isRequired,
favoriteFood: PropTypes.string.isRequired,
}).isRequired,
}
Index.defaultProps = {
AuthUserInfo: null,
}
// Use `withAuthUser` to get the authed user server-side, which
// disables static rendering.
// Use `withAuthUserInfo` to include the authed user as a prop
// to your component.
export default withAuthUser(withAuthUserInfo(Index))
withAuthUser.js
/* eslint react/jsx-props-no-spreading: 0 */
import React from 'react'
import PropTypes from 'prop-types'
import { get, set } from 'lodash/object'
import { AuthUserInfoContext, useFirebaseAuth } from '../auth/hooks'
import { createAuthUser, createAuthUserInfo } from '../auth/user'
// Gets the authenticated user from the Firebase JS SDK, when client-side,
// or from the request object, when server-side. Add the AuthUserInfo to
// context.
export default ComposedComponent => {
const WithAuthUserComp = props => {
const { AuthUserInfo, ...otherProps } = props
// We'll use the authed user from client-side auth (Firebase JS SDK)
// when available. On the server side, we'll use the authed user from
// the session. This allows us to server-render while also using Firebase's
// client-side auth functionality.
const { user: firebaseUser } = useFirebaseAuth()
const AuthUserFromClient = createAuthUser(firebaseUser)
const { AuthUser: AuthUserFromSession, token } = AuthUserInfo
const AuthUser = AuthUserFromClient || AuthUserFromSession || null
return (
<AuthUserInfoContext.Provider value={{ AuthUser, token }}>
<ComposedComponent {...otherProps} />
</AuthUserInfoContext.Provider>
)
}
WithAuthUserComp.getInitialProps = async ctx => {
const { req, res } = ctx
// Get the AuthUserInfo object.
let AuthUserInfo
if (typeof window === 'undefined') {
// If server-side, get AuthUserInfo from the session in the request.
// Don't include server middleware in the client JS bundle. See:
// https://arunoda.me/blog/ssr-and-server-only-modules
const { addSession } = require('../middleware/cookieSession')
addSession(req, res)
AuthUserInfo = createAuthUserInfo({
firebaseUser: get(req, 'session.decodedToken', null),
token: get(req, 'session.token', null),
})
} else {
// If client-side, get AuthUserInfo from stored data. We store it
// in _document.js. See:
// https://github.com/zeit/next.js/issues/2252#issuecomment-353992669
try {
const jsonData = JSON.parse(
window.document.getElementById('__MY_AUTH_USER_INFO').textContent
)
if (jsonData) {
AuthUserInfo = jsonData
} else {
// Use the default (unauthed) user info if there's no data.
AuthUserInfo = createAuthUserInfo()
}
} catch (e) {
// If there's some error, use the default (unauthed) user info.
AuthUserInfo = createAuthUserInfo()
}
}
// Explicitly add the user to a custom prop in the getInitialProps
// context for ease of use in child components.
set(ctx, 'myCustomData.AuthUserInfo', AuthUserInfo)
// Evaluate the composed component's getInitialProps().
let composedInitialProps = {}
if (ComposedComponent.getInitialProps) {
composedInitialProps = await ComposedComponent.getInitialProps(ctx)
}
return {
...composedInitialProps,
AuthUserInfo,
}
}
WithAuthUserComp.displayName = `WithAuthUser(${ComposedComponent.displayName})`
WithAuthUserComp.propTypes = {
AuthUserInfo: PropTypes.shape({
AuthUser: PropTypes.shape({
id: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
emailVerified: PropTypes.bool.isRequired,
}),
token: PropTypes.string,
}).isRequired,
}
WithAuthUserComp.defaultProps = {}
return WithAuthUserComp
}
withAuthUserInfo.js
/* eslint react/jsx-props-no-spreading: 0 */
import React from 'react'
import PropTypes from 'prop-types'
import { get } from 'lodash/object'
import { AuthUserInfoContext } from '../auth/hooks'
// Provides an AuthUserInfo prop to the composed component.
export default ComposedComponent => {
const WithAuthUserInfoComp = props => {
const { AuthUserInfo: AuthUserInfoFromSession, ...otherProps } = props
return (
<AuthUserInfoContext.Consumer>
{AuthUserInfo => (
<ComposedComponent
{...otherProps}
AuthUserInfo={AuthUserInfo || AuthUserInfoFromSession}
/>
)}
</AuthUserInfoContext.Consumer>
)
}
WithAuthUserInfoComp.getInitialProps = async ctx => {
const AuthUserInfo = get(ctx, 'myCustomData.AuthUserInfo', null)
// Evaluate the composed component's getInitialProps().
let composedInitialProps = {}
if (ComposedComponent.getInitialProps) {
composedInitialProps = await ComposedComponent.getInitialProps(ctx)
}
return {
...composedInitialProps,
AuthUserInfo,
}
}
WithAuthUserInfoComp.displayName = `WithAuthUserInfo(${ComposedComponent.displayName})`
WithAuthUserInfoComp.propTypes = {
AuthUserInfo: PropTypes.shape({
AuthUser: PropTypes.shape({
id: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
emailVerified: PropTypes.bool.isRequired,
}),
token: PropTypes.string,
}),
}
WithAuthUserInfoComp.defaultProps = {}
return WithAuthUserInfoComp
}
我很确定服务器端渲染和(可能带有诺言)发生了一些我不了解的事情。如果需要,我可以将数据提取推送到客户端,但是我想了解为什么这不起作用。登录后是否应该立即强制刷新? Next团队本身已在getInitialProps
index.js
方法中添加了以下代码段
// You can also get the token (e.g., to authorize a request when fetching data)
const AuthUserToken = get(AuthUserInfo, 'token', null)
所以我猜它打算在登录后立即生效。