Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
GGCE
GGCE Web
Commits
cbdcdf6f
Commit
cbdcdf6f
authored
Mar 27, 2020
by
Matija Obreza
Browse files
Merge branch '6-ssr-404' into 'master'
Resolve "SSR 404" Closes
#6
See merge request grin-global/grin-global-ui!5
parents
4406bef6
357c2a91
Changes
15
Hide whitespace changes
Inline
Side-by-side
packages/client/locales/en/common.json
View file @
cbdcdf6f
...
...
@@ -6,6 +6,7 @@
"submit"
:
"Submit"
},
"label"
:
{
"loadingData"
:
null
"loadingData"
:
null
,
"notFound"
:
"Not found"
}
}
packages/client/src/model/common/IRoute.ts
View file @
cbdcdf6f
...
...
@@ -7,4 +7,5 @@ export default interface IRoute extends RouteProps {
key
?:
string
;
routes
?:
IRoute
[];
component
?:
any
;
status
?:
number
;
}
packages/client/src/model/common/SsrError.ts
0 → 100644
View file @
cbdcdf6f
export
default
interface
SsrError
{
status
:
number
;
route
:
string
;
}
packages/client/src/ui/common/error/NotFound.tsx
0 → 100644
View file @
cbdcdf6f
import
*
as
React
from
'
react
'
;
import
{
WithTranslation
,
withTranslation
}
from
'
react-i18next
'
;
interface
INotFoundProps
extends
React
.
ClassAttributes
<
any
>
,
WithTranslation
{
}
class
NotFound
extends
React
.
Component
<
INotFoundProps
>
{
public
render
()
{
const
{
t
}
=
this
.
props
;
return
(
<
h3
>
{
t
(
'
common:label.notFound
'
)
}
</
h3
>
)
}
}
export
default
withTranslation
()(
NotFound
)
packages/client/src/ui/routes.ts
View file @
cbdcdf6f
...
...
@@ -3,12 +3,19 @@ import IRoute from '@gringlobal/client/model/common/IRoute';
import
WelcomePage
from
'
@gringlobal/client/ui/pages/Welcome
'
;
import
NotFound
from
'
@gringlobal/client/ui/common/error/NotFound
'
;
export
const
publicCoreRoutes
:
IRoute
[]
=
[
{
component
:
WelcomePage
,
path
:
'
/
'
,
exact
:
true
,
},
{
component
:
NotFound
,
path
:
'
*
'
,
status
:
404
,
},
]
;
...
...
packages/client/src/utilities/requestUtils.ts
View file @
cbdcdf6f
...
...
@@ -40,7 +40,7 @@ axiosBackend.interceptors.response.use(
window
.
location
.
replace
(
'
/
'
);
}
return
Promise
.
reject
(
error
)
;
throw
error
;
},
);
...
...
packages/ui-express/server/middleware/prerenderer.tsx
View file @
cbdcdf6f
...
...
@@ -38,6 +38,7 @@ import sagas from 'core/action/saga';
// react-loadable webpack stats
import
*
as
path
from
'
path
'
;
import
{
readFileSync
}
from
'
fs
'
;
import
{
receiveSsrError
}
from
'
core/action/serverInfo
'
;
const
reactLoadableStats
=
JSON
.
parse
(
readFileSync
(
path
.
join
(
'
./
'
,
'
react-loadable.json
'
),
{
encoding
:
'
utf8
'
}));
// console.log(`react-loadable stats: `, reactLoadableStats);
...
...
@@ -66,7 +67,7 @@ const prerenderer = (html, errHtml) => (req, res) => {
req
.
i18n
.
changeLanguage
(
locale
);
}
function
renderView
()
{
function
renderView
(
error
?
)
{
// const jss = createJss(jssPreset());
const
sheets
=
new
SheetsRegistry
();
const
generateClassName
=
createGenerateClassName
();
...
...
@@ -161,19 +162,32 @@ const prerenderer = (html, errHtml) => (req, res) => {
console
.
log
(
`Rendering
${
pathWithoutLang
}
for
${
pathname
}
`
);
const
branch
=
matchRoutes
(
routes
,
pathWithoutLang
);
if
(
branch
&&
branch
.
length
>
0
)
{
if
(
branch
[
branch
.
length
-
1
].
route
.
status
)
{
res
.
status
(
branch
[
branch
.
length
-
1
].
route
.
status
);
}
}
store
.
dispatch
(
receiveLang
(
language
));
fetchComponentData
(
store
.
dispatch
,
branch
,
search
,
store
.
getState
())
.
catch
((
err
)
=>
{
console
.
log
(
'
Error fetching component data
'
,
ApiError
.
axiosError
(
err
));
res
.
status
(
500
).
end
(
makeErrorHtml
(
errHtml
,
{
status
:
err
.
code
||
500
,
data
:
err
.
message
},
true
));
// const errFilledHtml = makeErrorHtml(errHtml, err);
// res.status(500).set('Content-Type', 'text/html').send(errFilledHtml);
})
.
then
(()
=>
{
return
sagaReady
.
toPromise
().
then
(()
=>
{
console
.
log
(
'
Fetched all component data
'
);
return
renderView
();
});
})
.
catch
((
err
)
=>
{
if
(
+
err
.
status
===
404
)
{
console
.
log
(
'
Error while fetching component data, rendering 404 page for:
'
,
pathWithoutLang
);
store
.
dispatch
(
receiveSsrError
(
404
,
pathWithoutLang
));
res
.
status
(
404
);
return
renderView
();
}
console
.
log
(
'
Error fetching component data
'
,
ApiError
.
axiosError
(
err
));
res
.
status
(
500
).
end
(
makeErrorHtml
(
errHtml
,
{
status
:
err
.
code
||
500
,
data
:
err
.
message
},
true
));
// const errFilledHtml = makeErrorHtml(errHtml, err);
// res.status(500).set('Content-Type', 'text/html').send(errFilledHtml);
}).
then
((
html
)
=>
{
const
serverRenderTime
=
`
${
Date
.
now
()
-
startTime
}
ms`
;
console
.
log
(
'
Server render time:
'
,
startTime
,
Date
.
now
(),
serverRenderTime
);
...
...
@@ -208,7 +222,7 @@ const prerenderer = (html, errHtml) => (req, res) => {
};
const
makeErrorHtml
=
(
template
,
error
,
withExplanation
=
false
)
=>
{
let
theFilledHtml
=
template
.
replace
(
'
ERROR_MESSAGE
'
,
error
.
status
).
replace
(
'
ERROR_DETAILS
'
,
error
.
data
);
let
theFilledHtml
=
template
.
replace
(
'
ERROR_MESSAGE
'
,
error
.
status
).
replace
(
'
ERROR_DETAILS
'
,
JSON
.
stringify
(
error
.
data
)
)
;
const
explanation
=
'
The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state.
'
;
theFilledHtml
=
theFilledHtml
.
replace
(
'
ERROR_EXPLANATION
'
,
withExplanation
?
explanation
:
''
);
...
...
packages/ui-express/src/core/action/saga.ts
View file @
cbdcdf6f
...
...
@@ -37,10 +37,12 @@ function *appendAxiosConfig(action) {
resp
=
action
.
onSuccess
(
resp
);
// postprocess
}
yield
put
({
type
:
action
.
target
,
payload
:
{
apiCall
:
ApiCall
.
success
(
resp
)
}
});
}
catch
(
e
)
{
}
catch
(
error
)
{
console
.
log
(
'
Api error while requesting:
'
,
error
.
response
);
if
(
action
.
onFail
)
{
action
.
onFail
(
e
);
// postprocess
action
.
onFail
(
e
rror
);
// postprocess
}
yield
put
({
type
:
action
.
target
,
payload
:
{
apiCall
:
ApiCall
.
error
(
e
)
}
});
yield
put
({
type
:
action
.
target
,
payload
:
{
apiCall
:
ApiCall
.
error
(
error
)
}
});
throw
error
.
response
;
}
}
packages/ui-express/src/core/action/serverInfo.ts
0 → 100644
View file @
cbdcdf6f
import
{
RECEIVE_SSR_ERROR_INFO
}
from
'
core/constants/serverInfo
'
;
export
const
receiveSsrError
=
(
status
:
number
,
route
:
string
)
=>
({
type
:
RECEIVE_SSR_ERROR_INFO
,
payload
:
{
error
:
{
status
,
route
},
},
});
packages/ui-express/src/core/constants/serverInfo.ts
0 → 100644
View file @
cbdcdf6f
export
const
RECEIVE_SSR_ERROR_INFO
=
'
core/serverInfo/RECEIVE_SSR_ERROR_INFO
'
;
packages/ui-express/src/core/reducer/index.ts
View file @
cbdcdf6f
import
{
combineReducers
}
from
'
redux
'
;
// express reducers
import
serverInfo
from
'
core/reducer/serverInfo
'
;
import
coreReducers
from
'
@gringlobal/client/reducer
'
;
// model reducers
...
...
@@ -6,6 +8,9 @@ import cooperator from 'cooperator/reducer';
import
user
from
'
user/reducer
'
;
const
rootReducer
=
(
history
?)
=>
(
combineReducers
({
// express reducers
serverInfo
,
// model reducers
cooperator
,
user
,
...
...
packages/ui-express/src/core/reducer/serverInfo.ts
0 → 100644
View file @
cbdcdf6f
import
update
from
'
immutability-helper
'
;
// Model
import
SsrError
from
'
@gringlobal/client/model/common/SsrError
'
;
import
{
RECEIVE_SSR_ERROR_INFO
}
from
'
core/constants/serverInfo
'
;
const
INITIAL_STATE
:
{
ssrError
:
SsrError
,
}
=
{
ssrError
:
null
,
};
export
default
(
state
=
INITIAL_STATE
,
action
:
{
type
?:
string
,
payload
?:
any
}
=
{
type
:
''
,
payload
:
{}
})
=>
{
switch
(
action
.
type
)
{
case
RECEIVE_SSR_ERROR_INFO
:
{
return
update
(
state
,
{
ssrError
:
{
$set
:
action
.
payload
.
error
},
});
}
default
:
return
state
;
}
}
packages/ui-express/src/core/ui/ExpressApp.tsx
0 → 100644
View file @
cbdcdf6f
import
*
as
React
from
'
react
'
;
import
{
connect
}
from
'
react-redux
'
;
import
{
bindActionCreators
}
from
'
redux
'
;
// Model
import
SsrError
from
'
@gringlobal/client/model/common/SsrError
'
;
// Ui
import
App
from
'
@gringlobal/client/ui/App
'
;
import
WrappedErrorApp
from
'
core/ui/WrappedErrorApp
'
;
interface
IExpressAppProps
extends
React
.
ClassAttributes
<
any
>
{
ssrError
:
SsrError
;
}
class
ExpressApp
extends
React
.
Component
<
IExpressAppProps
>
{
public
render
()
{
const
{
ssrError
}
=
this
.
props
;
return
ssrError
?
<
WrappedErrorApp
{
...
this
.
props
}
/>
:
<
App
{
...
this
.
props
}
/>;
}
}
const
mapStateToProps
=
(
state
)
=>
({
ssrError
:
state
.
serverInfo
.
ssrError
,
});
const
mapDispatchToProps
=
(
dispatch
)
=>
bindActionCreators
({
},
dispatch
);
export
default
connect
(
mapStateToProps
,
mapDispatchToProps
)(
ExpressApp
);
packages/ui-express/src/core/ui/WrappedErrorApp.tsx
0 → 100644
View file @
cbdcdf6f
import
*
as
React
from
'
react
'
;
import
{
connect
}
from
'
react-redux
'
;
import
{
bindActionCreators
}
from
'
redux
'
;
import
{
withRouter
,
RouteComponentProps
}
from
'
react-router-dom
'
;
import
renderRoutes
from
'
@gringlobal/client/ui/renderRoutes
'
;
// Actions
import
{
updateHistory
}
from
'
@gringlobal/client/action/history
'
;
// Model
import
SsrError
from
'
@gringlobal/client/model/common/SsrError
'
;
// Ui
import
NotFound
from
'
@gringlobal/client/ui/common/error/NotFound
'
interface
IWrappedErrorAppProps
extends
React
.
ClassAttributes
<
any
>
,
RouteComponentProps
{
ssrError
:
SsrError
;
route
?:
any
;
updateHistory
:
(
location
:
string
)
=>
void
;
}
class
WrappedErrorApp
extends
React
.
Component
<
IWrappedErrorAppProps
,
any
>
{
public
componentDidUpdate
(
prevProps
:
IWrappedErrorAppProps
)
{
const
{
updateHistory
,
location
:
prevLocation
}
=
prevProps
;
const
{
location
}
=
this
.
props
;
if
(
prevLocation
!==
null
&&
location
!==
null
)
{
if
(
prevLocation
!==
location
)
{
updateHistory
(
`
${
prevLocation
.
pathname
}${
prevLocation
.
search
?
prevLocation
.
search
:
''
}
`
);
}
}
}
protected
getErrorPage
=
(
status
:
number
)
=>
{
switch
(
+
status
)
{
case
404
:
return
NotFound
;
default
:
return
null
;
}
};
protected
compileErrorRoute
=
(
ssrError
)
=>
({
component
:
this
.
getErrorPage
(
ssrError
.
status
),
path
:
ssrError
.
route
,
});
public
render
()
{
const
{
route
:
{
routes
},
ssrError
}
=
this
.
props
;
return
(
<
div
>
{
renderRoutes
([
this
.
compileErrorRoute
(
ssrError
),
...
routes
])
}
</
div
>
);
}
}
const
mapStateToProps
=
(
state
)
=>
({
});
const
mapDispatchToProps
=
(
dispatch
)
=>
bindActionCreators
({
updateHistory
,
},
dispatch
);
export
default
withRouter
(
connect
(
mapStateToProps
,
mapDispatchToProps
)(
WrappedErrorApp
));
packages/ui-express/src/core/ui/routes.ts
View file @
cbdcdf6f
...
...
@@ -11,12 +11,12 @@ import { cooperatorPublicRotes } from 'cooperator/routes';
// User
import
{
userPublicRotes
}
from
'
user/routes
'
;
import
App
from
'
@gringlobal/client/ui/
App
'
;
import
Express
App
from
'
core/ui/Express
App
'
;
export
const
routes
:
IRoute
[]
=
[
{
component
:
App
,
component
:
Express
App
,
routes
:
[
...
userPublicRotes
,
...
cooperatorPublicRotes
,
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment