Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AppSync, AWS Amplify and SSR #1613

Closed
romainquellec opened this issue Sep 11, 2018 · 48 comments
Closed

AppSync, AWS Amplify and SSR #1613

romainquellec opened this issue Sep 11, 2018 · 48 comments
Assignees
Labels
AppSync Related to AppSync issues feature-request Request a new feature SSR Issues related to Server Side Rendering

Comments

@romainquellec
Copy link

AppSync, AWS Amplify and SSR :
Hi ! Is Amplify (GraphQL Client) is SSR compatible ? Can't find any information on docs right now. Thanks !

@yuth yuth added question General question AppSync Related to AppSync issues labels Sep 11, 2018
@undefobj
Copy link
Contributor

We have a tutorial published for this with Next.js: https://medium.com/open-graphql/ssr-graphql-apps-with-next-js-aws-appsync-eaf7fbeb1bde

@romainquellec
Copy link
Author

romainquellec commented Sep 18, 2018

Well, in this example, you re not using amplify. I will migrate all this with apollo and appsync, but for now, I just want amplify with SSR. Is this possible ?

@romainquellec
Copy link
Author

The answer is : no, you can't.

@Enalmada
Copy link

Enalmada commented Oct 19, 2018

@romainquellec Can you elaborate? I am considering building an AppSync react native app for a client and an admin interface to manage things is a requirement. A Next.js Amplify AppSync admin client "backend" would be ideal. Is Amplify with Next.js really not possible?

Seems like people might have gotten amplify working with SSR by using cookie authentation and an isomorphic fetch: #493 (comment) Are there problems beyond that?

@romainquellec romainquellec reopened this Oct 20, 2018
@romainquellec
Copy link
Author

@undefobj ?

@happy-little-one
Copy link

i only want seo, can amplify work with sth like prerender.io?

@ajhool
Copy link

ajhool commented Feb 7, 2019

Any luck with amplify and next.js? @Enalmada @romainquellec

@romainquellec
Copy link
Author

I cant help, I'm now using apollo and appsync for the GraphQL part.

@skdigital
Copy link

Its a real shame that nextjs is not compatible with AWS Amplify, its a massive short fall in my eyes.

@kjetilge
Copy link

I'm having the same issue. Maybe it would it be possible/easy to trigger prerendering of routes from a lambda function ? Perhaps using PrerenderSPAPlugin ?

@romainquellec
Copy link
Author

The specific GraphQL part was not SSR compatible (months ago).
I'm using Auth and Storage with Next with no problem (for now).

    "@aws-amplify/auth": "^1.2.15",
    "@aws-amplify/core": "^1.0.22",
    "@aws-amplify/storage": "^1.0.25",

...

    "apollo-cache-inmemory": "^1.4.2",
    "apollo-client": "^2.4.12",
    "apollo-link-http": "^1.5.11",
    "aws-appsync": "^1.7.1",

is OK.

@romainquellec
Copy link
Author

And you still could load some components with component with React if something is broken.

@ngocketit
Copy link

I'm using Amplify with Appsync and Nextjs and it works pretty ok. I had a setup where I use Api key on server and Cognito user pool on client. SSR is for public, anonymous user so you don't need to handle authentication etc. For all the protected pages that require user login, I render it on client.

@romainquellec
Copy link
Author

Closing this. There is (at least) one issue on SSR improvement for aws-amplify.

@ajhool
Copy link

ajhool commented Mar 7, 2019

I'm not sure why this issue was closed. This is an ongoing issue that I think a lot of people will stumble upon as nextjs and amplify grow in popularity.

@skdigital
Copy link

I agree this shouldn't be closed.. I, along with many others are holding our breath for amplify support for server rendered apps.

@ajhool
Copy link

ajhool commented Mar 9, 2019

@skdigital what are you looking for in particular? I have a nextjs workaround that I think is robust, although the workaround is just to deter Amplify from doing anything on the server

@skdigital
Copy link

@ajhool

I am looking to use nextjs and host via zeit now.

Use only Amplify for Auth, Storage, and Appsync realtime DB graphql with offline functionality.

Is this possible and advisable for production? (hosting somewhere else, so we can use nextjs)?

@ajhool
Copy link

ajhool commented May 3, 2019

I would highly recommend building a test app, first, that attempts the integrations you plan on using. If you'd like, I can create an open source example to show my integration strategies. I'm using react.

The only problem I've had, so far, is Amplify Auth + next. I have used Storage and Api(Appsync - not realtime) without any issues. All of my Api calls are coming from the client, so I'm not sure if Api/Appsync will work on the server, especially if the calls require authentication.

One of the main frustrations has been that the Amplify team does not currently fully support not-breaking-SSR** in the Amplify framework. However, the team is also extraordinarily helpful and responsive, they just have not been responsive on the SSR issue; I expect that to change as more people request SSR compatibility. Also, because amplify is an open source project provided by AWS, it seems that the official AWS support center does not actually provide support -- this policy, to me, does not make sense, but the lack of official support is worth considering for a production environment.

The other frustration is that next/now/zeit team is not very responsive or supportive. The software is convenient and worth it, but when it breaks it can be hard to find a fix. Thus, I highly recommend a barebones test app before integrating your entire app (as I tried to do).

**By not-breaking-ssr, I mean that the framework does not need to actual render on the server-side, but it should not break server-side rendering. Currently, simply importing certain Amplify components (eg. aws-amplify-react/Authenticator) has broken SSR without any clear error or debug opportunities.

@gcid12
Copy link

gcid12 commented May 29, 2019

@romainquellec , I also think you should keep this ticket open. There's a lot you can do with Amplify But well supported, well documented, production ready Server Side is a must for a lot of us. Would love to hear from @undefobj if this is in the roadmap, he's usually very responsive.

@undefobj undefobj reopened this May 29, 2019
@undefobj
Copy link
Contributor

SSR support is on our roadmap, and we'd like to do it this year but for transparency it's not in the next few months. It will require a fair amount of work on the internals and potentially some breaking changes so we would like to do it as part of a larger release this summer/fall with other changes. That being said if anyone in the community wishes to fork Amplify and try to get it working, then give feedback on what was required to get things working (even if it's a PR we don't merge) then that would help us accelerate the work.

I've reopened this issue for tracking.

@sammartinez sammartinez added feature-request Request a new feature and removed question General question labels Jun 18, 2019
@gtinkham
Copy link

@mlabieniec I added the line for global.navigator in my next.config.js which seems to have fixed my running errors. However, I get that same navigator issue when I try to build my project.
Is there some other place I need to reference navigator to solve that issue?

@mlabieniec
Copy link
Contributor

mlabieniec commented Nov 15, 2019

@gtinkham there is a fix for that in the latest amplify which you can get with npm i --save aws-amplify@next (2.0). This will fix that issue. However, there is still a build issue with the css in the node_modules (@aws-amplify/ui/dist/style.css) being loaded, which is an issue with next.js in discussion here: vercel/next-plugins#266

Seeing if there is anything we can do to figure out a work-around for this in the meantime, next also has an RFC related to better support for this here: vercel/next.js#8626

@mlabieniec
Copy link
Contributor

@gtinkham You can fix the build by using the following next.config.js (this works for both dev and build:

const withCSS = require('@zeit/next-css')
const resolve = require('resolve')
global.navigator = () => null
module.exports = withCSS({
    webpack (config, options) {
        const { dir, isServer } = options
        config.externals = []
        if (isServer) {
          config.externals.push((context, request, callback) => {
            resolve(request, { basedir: dir, preserveSymlinks: true }, (err, res) => {
              if (err) {
                return callback()
              }
              if (
                res.match(/node_modules[/\\].*\.css/)
                && !res.match(/node_modules[/\\]webpack/)
                && !res.match(/node_modules[/\\]@aws-amplify/)
              ) {
                return callback(null, `css ${request}`)
              }
    
              callback()
            })
          })
        }
    
        return config
      }
});

@shawnmmatthews
Copy link

@undefobj are there any updates on this with regards to official support?

@samstr
Copy link

samstr commented Jan 25, 2020

We want SSR! ✊

@tatsuyoshi-mashiko
Copy link

tatsuyoshi-mashiko commented Mar 17, 2020

Basically, Web application should be optimized SEO.
2 months ago, I don't used amplify when I created my product. Because amplify not support server side render.

@danielblignaut
Copy link

danielblignaut commented Apr 2, 2020

Would be good to get opinions on this... May seem slightly hacky but I've managed to at least get the Auth category storage running on SSR... From what I can tell, a great step forward for SSR in this library would be allowing us to universally configure the data storage across all categories. for example, in @aws-amplify/core package, storage is configured to use either window.localstorage and if no window.localstorage is available (i.e. on the server) it defaults to just using a js object. Would be great to be able to override this to use cookie storage instead? as we could then use universal cookies on the browser and server in nextJS. Currently, I'm doing something like this (very rough and extremely hacky until https://github.com/aws-amplify/amplify-js/blob/master/packages/core/src/StorageHelper/index.ts is allowed to pass storage object in:

Basically I'm overwriting window.localstorage to a custom storage object, configuring amplify to use that storage object then removing it such that it doesn't mess other dependency configs, etc. This is done on both the server and browser whilst bootstrapping Amplify.

I also need to add config.storage to my config, as the packages/amazon-cognito-identity-js which seems to do a lot of the underlying heavy lifting for the auth category takes a storage parameter (see https://github.com/aws-amplify/amplify-js/blob/master/packages/amazon-cognito-identity-js/src/CognitoUserPool.js )

My storage class then uses a universal client and server-side cookie library called nookies in nextJS.

configureAmplify.js

import config from '@/amplify/aws-exports'
import Amplify from '@aws-amplify/core'
import Auth from '@aws-amplify/auth'
import API from '@aws-amplify/api'
import Storage from '@aws-amplify/storage'
import { UniversalCookieStorage } from '@/lib/cookieStorage'
import { NextPageContext } from 'next'

export default (ctx?: NextPageContext)=> {
	if(ctx) UniversalCookieStorage.setContext(ctx)
	let isServer = false
	let localStorage = null
	//@ts-ignore
	config.storage = UniversalCookieStorage
	if(typeof window === 'undefined') {
		//@ts-ignore
		global.window = {
			localStorage: UniversalCookieStorage
		}
		isServer = true
	}
	else {
		localStorage = window.localStorage
		Object.defineProperty(window, 'localStorage', {
			value: UniversalCookieStorage,
			configurable: true,
			enumerable: true,
			writable: false
		})
	}
	Amplify.configure(config)
	//Amplify.Logger.LOG_LEVEL = 'ERROR'
	Auth.configure(config)
	Storage.configure(config)
	API.configure(config)

	if(isServer) {
		//@ts-ignore
		global.window = undefined
	}
	else {

		Object.defineProperty(window, 'localStorage', {
			value: localStorage,
			configurable: true,
			enumerable: true,
			writable: false
		})
	}
}

cookieStorage.ts

import { NextPageContext } from 'next'
import { setCookie, parseCookies, destroyCookie } from 'nookies'


export class UniversalCookieStorage {
	public static ctx: NextPageContext | null = null
	public static maxAge = 30 * 24 * 60 * 60

	public static setContext(ctx: NextPageContext) {
		UniversalCookieStorage.ctx = ctx
	}
	

	/**
	 * This is used to set a specific item in storage
	 * @param {string} key - the key for the item
	 * @param {object} value - the value
	 * @returns {string} value that was set
	 */
	public static setItem(key: string, value: any) {
		if(typeof value != 'string' && typeof value == 'boolean' && typeof value == 'number') {
			value = JSON.stringify(value)
		}
		setCookie(UniversalCookieStorage.ctx, key, value, {
			maxAge: UniversalCookieStorage.maxAge,
			path: '/',
		})
	}

	/**
	 * This is used to get a specific key from storage
	 * @param {string} key - the key for the item
	 * This is used to clear the storage
	 * @returns {string} the data item
	 */
	public static getItem(key: string) {
		const cookies = parseCookies(UniversalCookieStorage.ctx)
		// if(cookies[key] == null) throw new Error(`${key} does not exist in cookies`)

		return cookies[key]
	}

	/**
	 * This is used to remove an item from storage
	 * @param {string} key - the key being set
	 * @returns {string} value - value that was deleted
	 */
	public static removeItem(key: string) {
		const cookies = parseCookies(UniversalCookieStorage.ctx)

		// if(cookies[key] == null) throw new Error(`${key} does not exist in cookies`)
		destroyCookie(UniversalCookieStorage.ctx, key, {
			path: '/'
		})
		return cookies[key]
	}

	/**
	 * This is used to clear the storage
	 * @returns {string} nothing
	 */
	public static clear() {
		const cookies = parseCookies(UniversalCookieStorage.ctx)
		const keys = Object.keys(cookies)

		keys.forEach((key)=> {
			destroyCookie(UniversalCookieStorage.ctx, key, {
			path: '/'
		})
		})

		return {}
	}
}

example component with SSR rendering for a user (i.e. show authenticated page otherwise redirect user)

import React, { useState, useEffect } from 'react'
import Auth from '@aws-amplify/auth'
import configureAmplify from '@/lib/configureAmplify'

interface Props {
	isLoggedIn: boolean
}

const Example: React.FC<Props> = (props)=> {

	if(props.isLoggedIn) {
		return <div>Secret content</div>
	}
	else {
		return <div>need to log in!</div>
	}
}

//@ts-ignore
Example.getInitialProps = async (ctx: NextPageContext)=> {
	configureAmplify(ctx)
	let user = null
	let isLoggedIn = false
	try {
		user = await Auth.currentAuthenticatedUser()

		if(user != null) isLoggedIn = true
	}
	catch(e) {
		// console.error(e)
	}

	return {
		isLoggedIn
	}
	
}

export default Example

and lastly, _app.js

import React from 'react'
import App, { AppInitialProps, AppProps, AppContext } from 'next/app'
import configureAmplify from '@/lib/configureAmplify'

interface MyAppProps extends AppProps, InitialPageLoadAppProps { 
}

interface InitialPageLoadAppProps extends AppInitialProps {

}

interface MyAppState {
}



class MyApp extends App<MyAppProps, {}, MyAppState> {
	// Only uncomment this method if you have blocking data requirements for
	// every single page in your application. This disables the ability to
	// perform automatic static optimization, causing every page in your app to
	// be server-side rendered.
	
	// public static async getInitialProps(appContext: AppContext) {
	// 	//server-side auth checks and redirects here


	// 	// calls page's `getInitialProps` and fills `appProps.pageProps`
	// 	const appProps = await App.getInitialProps(appContext)

	// 	return { ...appProps }
	// }

	public constructor(props: MyAppProps) {
		super(props)
		this.state = {
		}
		
		configureAmplify()
	}

	public async componentDidMount() {
		try {
			this.setState({
				...this.state,
			})
		}
		catch(e) {
			console.error(e)
		}
	}

	public render() {
		const { Component, pageProps } = this.props
		return <Component {...pageProps} />
	}
}




export default MyApp

package.json dependencies:

{
"@aws-amplify/api": "3.1.3",
    "@aws-amplify/auth": "3.2.0",
    "@aws-amplify/core": "3.2.0",
    "@aws-amplify/storage": "3.1.3",
}

I haven't played around with the API library and the above as yet but if we can get it to play nice, at least that will let one use SSR for the auth and API categories.... which is my mind will be most of the use cases for SSR anyway.

Interested to hear feedback.

@lcx1204
Copy link

lcx1204 commented Apr 4, 2020

@undefobj, do you think SSR could become available next few months? thank you

@undefobj
Copy link
Contributor

undefobj commented Apr 4, 2020

We don't have any specific timelines but we are actively looking into this.

@MontoyaAndres
Copy link

Hi, I'm looking for adding Next.js to my apps with Amplify. I don't know if this example works only with create-react-app: https://github.com/aws-samples/aws-amplify-auth-starters/tree/react because it does not use localstorage manually, it just uses the methods that Amplify auth offers, I don't know if these methods are the enough smart to detect if the app is using ssr or not, even with ssg. Algo what @danielblignaut is showing in his example would work great, it's very similar to this: https://spectrum.chat/next-js/general/amplify-auth-and-next-js~b7ef99fe-d6bf-459d-8dd3-d5a04fcbaf89 I don't know if you used it as a reference.

Also, Next.js 9.3 has new methods that are more powerful than getInitialProps which are:

getStaticProps fetch data over ssg
getServerSideProps fetch data over ssr instead of using getInitialProps
getStaticPaths to control the path and params of ssg

Maybe these methods can help to bring this functionality as soon as possible :)

@samstr
Copy link

samstr commented Apr 13, 2020

Just deploy to now. It works perfectly with serverless functions & SSR as it's built by zeit (nextjs). It uses AWS Lambda behind the scenes.

@judygab
Copy link

judygab commented Aug 16, 2020

Its a real shame that nextjs is not compatible with AWS Amplify, its a massive short fall in my eyes.

why it's not compatible?

@judygab
Copy link

judygab commented Aug 16, 2020

@gtinkham

I recently switched from the amplify client to the apollo client and it has made life so much easier. The apollo client has a few bugs but it beats the amplify client in nearly every way IMO and I've recommended to the amplify team that they should remove the amplify client because things like SSR are not workable and there are existing solutions. I don't actually know if you can use the amplify client on a server -- I do know that amplify officially does not support server-side code.

We also were finding that simply importing amplify on the server was breaking the code due to a window reference -- I've never had a node package break server-side rendering or code as badly as amplify does (a window reference at import time!!!). The only way I was able to use Amplify with next and SSR was to defer import to the client side using next's dynamic import.

Before the switch, I was using apollo to make public calls from the server and then was using the amplify client on the client.

can you provide and example of app or index file?

@ericclemmons
Copy link
Contributor

For reference, this is in @preview:

#5435 (comment)

@Mdev303
Copy link

Mdev303 commented Aug 21, 2020

@undefobj Hopefully, the SSR implementation will be framework agnostic so it can also be used with nuxtjs

@ericclemmons
Copy link
Contributor

This has been addressed as part of #6146!

Please see my synopsis on changes here: #5435 (comment)

@github-actions
Copy link

This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.

Looking for a help forum? We recommend joining the Amplify Community Discord server *-help channels or Discussions for those types of questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
AppSync Related to AppSync issues feature-request Request a new feature SSR Issues related to Server Side Rendering
Projects
None yet
Development

No branches or pull requests