├── .gitignore ├── README.md ├── components ├── App.js ├── ArtistDetails.js ├── Card.js ├── Grid.js ├── Header.js ├── Loading.js ├── Nav.js ├── PageImage.js ├── RecordDetails.js └── ReviewDetails.js ├── docs └── settings.png ├── lib ├── initApollo.js └── withData.js ├── package.json ├── pages ├── artists.js ├── artists │ └── details.js ├── index.js ├── records.js ├── records │ └── details.js └── reviews │ └── details.js ├── server.js ├── static ├── microphone.svg ├── records.svg └── turntable.svg └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | _STORE 2 | node_modules 3 | *~ 4 | .next 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Server-side rendered App with Next.js, Apollo and GraphCMS 2 | 3 | 🚀 **[Live Demo](https://vinylbase.now.sh)** 4 | 5 | This example shows how to build a small music blog with [Next.js](https://github.com/zeit/next.js/), [Apollo](http://www.apollodata.com/) and [GraphCMS](https://graphcms.com). 6 | 7 | To connect your app you have to setup a new GraphCMS project and create the required content models as described [here](https://graphcms.com/docs/examples/Server-side_rendered_app_with_nextjs_and_apollo/). 8 | 9 | To get this information, log into GraphCMS and go to your project settings. 10 | 11 | ![Screenshot](docs/settings.png) 12 | 13 | Copy the Endpoint URL for the `Simple Endpoint` from the `ENDPOINTS` section. Insert the URL into the variable `GRAPHCMS_API` in the file `lib/initClient`. 14 | 15 | ## Installation 16 | 17 | `npm install` 18 | 19 | ## Starting 20 | 21 | `npm run start` 22 | 23 | ## Deployment 24 | 25 | Install now: 26 | 27 | `npm install -g now` 28 | 29 | Deploy the app: 30 | 31 | `now` 32 | -------------------------------------------------------------------------------- /components/App.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | 3 | const App = ({ children }) => ( 4 |
5 | 6 | Vinylbase 7 | 8 | 9 | {children} 10 | 13 | 44 |
45 | ) 46 | 47 | export default App 48 | -------------------------------------------------------------------------------- /components/ArtistDetails.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactMarkdown from 'react-markdown' 3 | import Grid from './Grid' 4 | 5 | const ArtistDetails = ({ artist }) => ( 6 |
7 | 8 |

Records:

9 | 10 | 11 |
12 | ) 13 | 14 | export default ArtistDetails 15 | -------------------------------------------------------------------------------- /components/Card.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import StarRatingComponent from 'react-star-rating-component' 3 | 4 | const Card = ({ entry: { createdAt, title, id, image, rating } }) => ( 5 |
6 | { image ? : null } 7 | 8 |
{title}
9 | { rating && 10 |
11 | 18 |
19 | } 20 | 57 |
58 | ) 59 | 60 | export default Card 61 | -------------------------------------------------------------------------------- /components/Grid.js: -------------------------------------------------------------------------------- 1 | import Card from './Card' 2 | import Link from 'next/link' 3 | 4 | const Grid = ({ entries, type, pageImage }) => ( 5 |
6 | 19 | 20 | 32 |
33 | ) 34 | 35 | export default Grid 36 | -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import PageImage from './PageImage' 2 | 3 | const Header = ({ title, subLine, pageImage, isIcon }) => ( 4 |
5 |
6 |

{title}

7 | { subLine ?

{subLine}

: null} 8 |
9 | 10 | 11 | 12 | 36 |
37 | ) 38 | 39 | export default Header 40 | -------------------------------------------------------------------------------- /components/Loading.js: -------------------------------------------------------------------------------- 1 | const Loading = () =>
Loading...
2 | 3 | export default Loading 4 | -------------------------------------------------------------------------------- /components/Nav.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | const Nav = ({ pathname }) => ( 4 |
5 | 18 | 19 | 48 |
49 | ) 50 | 51 | export default Nav 52 | -------------------------------------------------------------------------------- /components/PageImage.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames' 2 | 3 | const PageImage = ({image, isIcon}) => { 4 | if (!image) { 5 | return null 6 | } 7 | 8 | return ( 9 |
10 | 11 | 12 | 32 |
33 | ) 34 | } 35 | 36 | export default PageImage -------------------------------------------------------------------------------- /components/RecordDetails.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const RecordDetails = ({ record }) => ( 4 |
5 | 13 | 14 | 33 |
34 | ) 35 | 36 | export default RecordDetails 37 | -------------------------------------------------------------------------------- /components/ReviewDetails.js: -------------------------------------------------------------------------------- 1 | import StarRatingComponent from 'react-star-rating-component' 2 | import ReactMarkdown from 'react-markdown' 3 | import Grid from './Grid' 4 | 5 | const ReviewDetails = ({ review: { title, review, rating, record: { artist } } }) => ( 6 |
7 |
8 | 14 |
15 | 16 | 17 |

The artist:

18 | 19 | 20 | 27 |
28 | ) 29 | 30 | export default ReviewDetails 31 | -------------------------------------------------------------------------------- /docs/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hygraph/example_01_nextjs_apollo/0e0c08cd4600d3535e4d6651c5ece26b6e42c1f4/docs/settings.png -------------------------------------------------------------------------------- /lib/initApollo.js: -------------------------------------------------------------------------------- 1 | import { ApolloClient } from 'apollo-client' 2 | import { HttpLink } from 'apollo-link-http' 3 | import { InMemoryCache } from 'apollo-cache-inmemory' 4 | import fetch from 'isomorphic-fetch' 5 | 6 | let apolloClient = null 7 | 8 | // Polyfill fetch() on the server (used by apollo-client) 9 | if (!process.browser) { 10 | global.fetch = fetch 11 | } 12 | 13 | // Replace this URL by your APIs simple endpoint URL: 14 | const GRAPHCMS_API = 'https://api.graphcms.com/simple/v1/vinylbase' 15 | 16 | function createClient (initialState) { 17 | const HttpLinkData = { 18 | uri: GRAPHCMS_API, 19 | opts: { 20 | credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers` 21 | } 22 | } 23 | return new ApolloClient({ 24 | connectToDevTools: process.browser, 25 | ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once) 26 | link: new HttpLink(HttpLinkData), 27 | cache: new InMemoryCache().restore(initialState || {}) 28 | }) 29 | } 30 | 31 | export default function initApollo (initialState) { 32 | // Make sure to create a new client for every server-side request so that data 33 | // isn't shared between connections (which would be bad) 34 | if (!process.browser) { 35 | return createClient(initialState) 36 | } 37 | 38 | // Reuse client on the client-side 39 | if (!apolloClient) { 40 | apolloClient = createClient(initialState) 41 | } 42 | return apolloClient 43 | } 44 | -------------------------------------------------------------------------------- /lib/withData.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { ApolloProvider, getDataFromTree } from 'react-apollo' 4 | import Head from 'next/head' 5 | import initApollo from './initApollo' 6 | 7 | // Gets the display name of a JSX component for dev tools 8 | function getComponentDisplayName (Component) { 9 | return Component.displayName || Component.name || 'Unknown' 10 | } 11 | 12 | export default ComposedComponent => { 13 | return class WithData extends Component { 14 | static displayName = `WithData(${getComponentDisplayName(ComposedComponent)})` 15 | static propTypes = { 16 | serverState: PropTypes.object.isRequired 17 | } 18 | 19 | static async getInitialProps (ctx) { 20 | let serverState = {} 21 | 22 | // evaluate getInitialProps() 23 | let composedInitialProps = {} 24 | if (ComposedComponent.getInitialProps) { 25 | composedInitialProps = await ComposedComponent.getInitialProps(ctx) 26 | } 27 | 28 | // Running all queries in the tree extracting the data 29 | if (!process.browser) { 30 | const apollo = initApollo() 31 | // url prop if any of our queries needs it 32 | const url = { query: ctx.query, pathname: ctx.pathname } 33 | 34 | try { 35 | // Run all GraphQL queries 36 | await getDataFromTree( 37 | 38 | 39 | 40 | ) 41 | } catch (error) { 42 | // Prevent Apollo Client GraphQL errors from crashing SSR. 43 | // Handle them in components via the data.error prop: 44 | // http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error 45 | } 46 | // getDataFromTree does not call componentWillUnmount 47 | // head side effect therefore need to be cleared manually 48 | Head.rewind() 49 | 50 | // Extract query data from the Apollo store 51 | serverState = { 52 | apollo: { 53 | data: apollo.cache.extract() 54 | } 55 | } 56 | } 57 | 58 | return { 59 | serverState, 60 | ...composedInitialProps 61 | } 62 | } 63 | 64 | constructor (props) { 65 | super(props) 66 | this.apollo = initApollo(this.props.serverState) 67 | } 68 | 69 | render () { 70 | return ( 71 | 72 | 73 | 74 | ) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vinylbase", 3 | "url": "https://github.com/GraphCMS/exmple_01_nextjs_apollo.git", 4 | "version": "0.0.2", 5 | "description": "Example app using Next, Apollo and GraphCMS", 6 | "scripts": { 7 | "dev": "node server.js", 8 | "next": "next", 9 | "build": "next build", 10 | "start": "NODE_ENV=production node server.js" 11 | }, 12 | "author": "GraphCMS", 13 | "license": "ISC", 14 | "standard": { 15 | "parser": "babel-eslint", 16 | "ignore": [ 17 | "**/node_modules/**" 18 | ] 19 | }, 20 | "devDependencies": { 21 | "babel-eslint": "^7.2.3", 22 | "prop-types": "^15.5.10", 23 | "standard": "^10.0.2" 24 | }, 25 | "dependencies": { 26 | "apollo-client-preset": "^1.0.1", 27 | "express": "^4.15.3", 28 | "graphql": "^0.11.7", 29 | "graphql-anywhere": "^4.0.0", 30 | "graphql-tag": "^2.5.0", 31 | "isomorphic-fetch": "^2.2.1", 32 | "next": "^4.1.4", 33 | "react": "^16.0.0", 34 | "react-apollo": "^2.0.0", 35 | "react-dom": "^16.0.0", 36 | "react-markdown": "^2.5.0", 37 | "react-star-rating-component": "1.2.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pages/artists.js: -------------------------------------------------------------------------------- 1 | import { graphql } from 'react-apollo' 2 | import gql from 'graphql-tag' 3 | import App from '../components/App' 4 | import Grid from '../components/Grid' 5 | import Header from '../components/Header' 6 | import Loading from '../components/Loading' 7 | import Nav from '../components/Nav' 8 | import withData from '../lib/withData' 9 | 10 | const AllArtists = ({ url: { pathname }, data: { loading, error, allArtists } }) => { 11 | if (error) return

Error loading artists.

12 | return ( 13 | 14 |