added SSR

This commit is contained in:
Sergey Yarkov 2020-02-29 13:15:36 +03:00
parent 6cdb91e767
commit 48c8d5d82b
13 changed files with 285 additions and 116 deletions

Binary file not shown.

View File

@ -1,21 +1,20 @@
import Card from "./card";
import Card from "./Card";
const Articles = ({ articles }) => {
console.log(articles)
const leftArticlesCount = Math.ceil(articles.length / 5);
const leftArticles = articles.slice(0, leftArticlesCount);
const rightArticles = articles.slice(leftArticlesCount, articles.length);
return (
<div>
<div className="uk-child-width-1-2" data-uk-grid>
<div className="uk-child-width-1-2 uk-grid" data-uk-grid>
<div>
{leftArticles.map((article, i) => {
return <Card article={article} key={`article__${article.id}`} />;
})}
</div>
<div>
<div className="uk-child-width-1-2@m uk-grid-match" data-uk-grid>
<div className="uk-child-width-1-2@m uk-grid-match uk-grid" data-uk-grid>
{rightArticles.map((article, i) => {
return <Card article={article} key={`article__${article.id}`} />;
})}

View File

@ -0,0 +1,27 @@
import Head from 'next/head'
import Nav from './Nav'
const Layout = props => {
return(
<>
<Head>
<title>{props.titlePage}</title>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Staatliches"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/css/uikit.min.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.min.js" />
<script src="https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/js/uikit-icons.min.js" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.js" />
</Head>
<Nav />
{props.children}
</>
)
}
export default Layout

View File

@ -1,6 +1,7 @@
import Link from "next/link"
import Query from "../components/query";
import Query from "./query";
import CATEGORIES_QUERY from "../apollo/queries/category/categories.js";
import { withApollo } from "../utils/apollo";
const Nav = () => {
return (
@ -9,7 +10,7 @@ const Nav = () => {
{({ data: { categories } }) => {
return (
<div>
<nav className="uk-container uk-container-large uk-navbar-container" data-uk-navbar>
<nav className="uk-container uk-container-large uk-navbar-container uk-navbar" data-uk-navbar>
<div className="uk-navbar-left">
<ul className="uk-navbar-nav">
<li>

View File

@ -1,9 +1,11 @@
require("dotenv").config();
const withCSS = require('@zeit/next-css')
const withFonts = require('next-fonts')
module.exports = withCSS({
env: {
API_URL: process.env.API_URL
}
},
devIndicators: {
autoPrerender: false,
},
})

View File

@ -82,6 +82,16 @@
"tslib": "^1.10.0"
}
},
"@apollo/react-ssr": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@apollo/react-ssr/-/react-ssr-3.1.3.tgz",
"integrity": "sha512-fUTmEYHxSTX1GA43B8vICxXXplpcEBnDwn0IgdAc3eG0p2YK97ZrJDRFCJ5vD7fyDZsrYhMf+rAI3sd+H2SS+A==",
"requires": {
"@apollo/react-common": "^3.1.3",
"@apollo/react-hooks": "^3.1.3",
"tslib": "^1.10.0"
}
},
"@babel/code-frame": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
@ -1434,6 +1444,22 @@
"picomatch": "^2.0.4"
}
},
"apollo-boost": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/apollo-boost/-/apollo-boost-0.4.7.tgz",
"integrity": "sha512-jfc3aqO0vpCV+W662EOG5gq4AH94yIsvSgAUuDvS3o/Z+8Joqn4zGC9CgLCDHusK30mFgtsEgwEe0pZoedohsQ==",
"requires": {
"apollo-cache": "^1.3.4",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.7",
"apollo-link": "^1.0.6",
"apollo-link-error": "^1.0.3",
"apollo-link-http": "^1.3.1",
"graphql-tag": "^2.4.2",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0"
}
},
"apollo-cache": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.4.tgz",
@ -1481,6 +1507,16 @@
"zen-observable-ts": "^0.8.20"
}
},
"apollo-link-error": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/apollo-link-error/-/apollo-link-error-1.1.12.tgz",
"integrity": "sha512-psNmHyuy3valGikt/XHJfe0pKJnRX19tLLs6P6EHRxg+6q6JMXNVLYPaQBkL0FkwdTCB0cbFJAGRYCBviG8TDA==",
"requires": {
"apollo-link": "^1.2.13",
"apollo-link-http-common": "^0.2.15",
"tslib": "^1.9.3"
}
},
"apollo-link-http": {
"version": "1.5.16",
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.16.tgz",

View File

@ -9,7 +9,9 @@
},
"dependencies": {
"@apollo/react-hooks": "^3.1.3",
"@apollo/react-ssr": "^3.1.3",
"@zeit/next-css": "^1.0.1",
"apollo-boost": "^0.4.7",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8",
"apollo-link-http": "^1.5.16",

View File

@ -1,39 +0,0 @@
import Head from "next/head";
import NProgress from 'nprogress'
import Router from 'next/router'
import "../assets/css/style.css";
import { ApolloProvider } from "@apollo/react-hooks";
import withData from "../utils/apollo";
import Nav from '../components/nav'
Router.events.on('routeChangeStart', url => NProgress.start())
Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done())
const App = ({ Component, pageProps, apollo }) => {
return (
<ApolloProvider client={apollo}>
<Head>
<title>Strapi blog</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Staatliches"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/css/uikit.min.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.min.js" />
<script src="https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/js/uikit-icons.min.js" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.js" />
</Head>
<Nav />
<Component {...pageProps} />
</ApolloProvider>
)
};
// Wraps all components in the tree with the data provider
export default withData(App);

View File

@ -1,39 +1,45 @@
import { useRouter } from "next/router";
import Query from "../../components/query";
import ReactMarkdown from "react-markdown";
import Moment from "react-moment";
import ARTICLE_QUERY from "../../apollo/queries/article/article";
import { useRouter } from "next/router";
import { withApollo } from "../../utils/apollo";
import Query from "../../components/query";
import Layout from "../../components/Layout"
const Article = () => {
const router = useRouter();
return (
<Query query={ARTICLE_QUERY} id={router.query.id}>
{({ data: { article } }) => {
return (
<div>
<div
id="banner"
className="uk-height-medium uk-flex uk-flex-center uk-flex-middle uk-background-cover uk-light uk-padding uk-margin"
data-src={process.env.API_URL + article.image.url}
data-srcset={process.env.API_URL + article.image.url}
data-uk-img
>
<h1>{article.title}</h1>
</div>
<div className="uk-section">
<div className="uk-container uk-container-small">
<ReactMarkdown source={article.content} />
<p>
<Moment format="MMM Do YYYY">{article.published_at}</Moment>
</p>
return (
<Query query={ARTICLE_QUERY} id={router.query.id}>
{({ data: { article } }) => {
return (
<Layout titlePage={article.title}>
<div>
<div
id="banner"
className="uk-height-medium uk-flex uk-flex-center uk-flex-middle uk-background-cover uk-light uk-padding uk-margin"
data-src={process.env.API_URL + article.image.url}
data-srcset={process.env.API_URL + article.image.url}
data-uk-img
>
<h1>{article.title}</h1>
</div>
<div className="uk-section">
<div className="uk-container uk-container-small">
<ReactMarkdown source={article.content} />
<p>
<Moment format="MMM Do YYYY">{article.published_at}</Moment>
</p>
</div>
</div>
</div>
</div>
</div>
);
}}
</Query>
</Layout>
);
}}
</Query>
);
};
export default Article;
export default withApollo(Article);

View File

@ -1,27 +1,32 @@
import { useRouter } from "next/router";
import Articles from "../../components/articles";
import Query from "../../components/query";
import CATEGORY_ARTICLES_QUERY from "../../apollo/queries/category/articles";
import { useRouter } from "next/router";
import { withApollo } from "../../utils/apollo";
import Articles from "../../components/articles";
import Query from "../../components/query";
import Layout from "../../components/Layout"
const Category = () => {
const router = useRouter();
return (
<Query query={CATEGORY_ARTICLES_QUERY} id={router.query.id}>
{({ data: { category } }) => {
return (
<div>
<div className="uk-section">
<div className="uk-container uk-container-large">
<h1>{category.name}</h1>
<Articles articles={category.articles} />
<Layout titlePage="Categories">
<Query query={CATEGORY_ARTICLES_QUERY} id={router.query.id}>
{({ data: { category } }) => {
return (
<div>
<div className="uk-section">
<div className="uk-container uk-container-large">
<h1>{category.name}</h1>
<Articles articles={category.articles} />
</div>
</div>
</div>
</div>
);
}}
</Query>
);
}}
</Query>
</Layout>
);
};
export default Category
export default withApollo(Category)

View File

@ -1,22 +1,26 @@
import Layout from "../components/Layout"
import Articles from "../components/articles";
import Query from "../components/query";
import ARTICLES_QUERY from "../apollo/queries/article/articles";
import { withApollo } from '../utils/apollo';
const Home = () => {
return (
<div>
<div className="uk-section">
<div className="uk-container uk-container-large">
<h1>Example Blog</h1>
<Query query={ARTICLES_QUERY}>
{({ data: { articles } }) => {
return <Articles articles={articles} />;
}}
</Query>
<Layout titlePage="Home page">
<div>
<div className="uk-section">
<div className="uk-container uk-container-large">
<h1>Example Blog</h1>
<Query query={ARTICLES_QUERY}>
{({ data: { articles } }) => {
return <Articles articles={articles} />;
}}
</Query>
</div>
</div>
</div>
</div>
</Layout>
);
};
export default Home
export default withApollo(Home)

View File

@ -1,22 +1,148 @@
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import withApollo from 'next-with-apollo'
import { createHttpLink } from 'apollo-link-http'
import React, { useMemo } from 'react'
import Head from 'next/head'
import { ApolloProvider } from '@apollo/react-hooks'
import { ApolloClient, InMemoryCache, HttpLink } from 'apollo-boost'
import fetch from 'isomorphic-unfetch'
import NProgress from 'nprogress'
import "../assets/css/style.css";
import Router from 'next/router'
Router.events.on('routeChangeStart', url => NProgress.start())
Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done())
const GRAPHQL_URL = process.env.BACKEND_URL || 'http://localhost:1337/graphql'
const link = new createHttpLink({
const link = new HttpLink({
fetch,
uri: GRAPHQL_URL
})
export default withApollo(
({initialState}) =>
new ApolloClient({
link,
cache: new InMemoryCache()
.restore(initialState || {})
})
)
let apolloClient = null
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* @param {Function|Class} PageComponent
* @param {Object} [config]
* @param {Boolean} [config.ssr=true]
*/
export function withApollo (PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = useMemo(
() => apolloClient || initApolloClient(apolloState),
[]
)
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
)
}
// Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') {
const displayName =
PageComponent.displayName || PageComponent.name || 'Component'
if (displayName === 'App') {
console.warn('This withApollo HOC only works with PageComponents.')
}
WithApollo.displayName = `withApollo(${displayName})`
}
// Allow Next.js to remove getInitialProps from the browser build
if (typeof window === 'undefined') {
if (ssr) {
WithApollo.getInitialProps = async ctx => {
const { AppTree } = ctx
let pageProps = {}
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx)
}
// Run all GraphQL queries in the component tree
// and extract the resulting data
const apolloClient = initApolloClient()
try {
// Run all GraphQL queries
await require('@apollo/react-ssr').getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient
}}
/>
)
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error)
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract()
return {
...pageProps,
apolloState
}
}
}
}
return WithApollo
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {Object} initialState
*/
function initApolloClient (initialState) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(initialState)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState)
}
return apolloClient
}
/**
* Creates and configures the ApolloClient
* @param {Object} [initialState={}]
*/
function createApolloClient (initialState = {}) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
const isBrowser = typeof window !== 'undefined'
return new ApolloClient({
connectToDevTools: isBrowser,
ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: GRAPHQL_URL, // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
// Use fetch() polyfill on the server
fetch: !isBrowser && fetch
}),
cache: new InMemoryCache().restore(initialState)
})
}

View File

@ -5,7 +5,7 @@
"main": "index.js",
"scripts": {
"postinstall": "cd backend && npm install && cd ../frontend && npm install",
"heroku-postbuild": "npm run --prefix ./frontend build",
"build": "npm run --prefix ./frontend build",
"start": "concurrently \"npm run --prefix ./backend/ start\" \"npm run --prefix ./frontend/ start\"",
"download": "concurrently \"npm run --prefix ./backend/ install\" \"npm run --prefix ./frontend/ install\"",
"dev": "concurrently \"yarn --cwd ./backend/ develop\" \"yarn --cwd ./frontend/ dev\""