🚀🚀 A Shopify App template for serverless, non-embedded Apps.

Overview

🚀 Free Shopify x Next.js App Template for serverless non-embedded Apps

Everything to build your next non-embedded Shopify App and Marketing pages in one place. This Template utilizes Middleware and APIs for OAuth, so no custom server is needed.

Intentionally barebones. ðŸĶī

Table of Contents

  • ðŸĪĐ Features
  • 👀 Requirements
  • ðŸĪ“ Getting Started
  • 🚀 One click deploy
  • 🧰 Built with

ðŸĪĐ Features

  • ⚡ Next.js - React Framework for static rendering
  • âœĻ Serverless Architecture
  • ðŸ’ģ App Subscrptions
  • ðŸ’ū Session Storage with Redis
  • 🚇 Ngrok for development
  • 🚀 Apollo/Client
  • 🊝 Webhooks set up

👀 Requirements

  • Shopify Partner Account
  • Shopify Dev Store
  • Ngrok account
  • Upstash Redis Database

ðŸĪ“ Getting Started

  • Click Use this template or this link
  • Create an App in your Shopify Partner Account
    • Set https://localhost as the App Url for now
    • Go to App Setup -> Embedded app and disable Embed your app in Shopify admin
  • Fill out your .env file
    • SHOPIFY_API_KEY: The Shopify Api key of the app, you have just created
    • SHOPIFY_API_SECRET_KEY: The Shopify Api secret key of the app, you have just created
    • SCOPES: The access scopes your app needs
    • HOST: The Url of your app. Leave this empty for development
    • SHOP: Your dev stores url
    • NGROK_AUTH_TOKEN: Your Ngrok auth token
    • UPSTASH_REDIS_REST_URL: Your Upstash Redis REST url.
    • UPSTASH_REDIS_REST_TOKEN: Your Upstash Redis REST token.
  • Run npm install
  • Run npm run dev
  • Visit https://{YOUR_APP_URL}/login to install your app

🚀 One click deploy

Clone and deploy this template in one click to Vercel for free!

Deploy with Vercel

Check out our Next.js deployment documentation for more details.

🧰 Built with

Comments
  • Update AppSettings through Shopify API

    Update AppSettings through Shopify API

    Why LocalTunnel instead of Ngrok? And how can we update AppSettings through Shopify API? If that is possible... Shopify makes it really painful to change it every time with ngrok always changing URL's all the time.

    opened by aimproxy 11
  • NgrokClientError: failed to start tunnel

    NgrokClientError: failed to start tunnel

    The error I am getting is (file paths shortened, ngrok auth token removed):

    npm run dev
    
    > [email protected] dev
    > next dev
    
    ready - started server on 0.0.0.0:3000, url: http://localhost:3000
    info  - Loaded env from .../shopify-non-embedded-app-template/.env
    NgrokClientError: failed to start tunnel
        at NgrokClient.request (.../shopify-non-embedded-app-template/node_modules/ngrok/src/client.js:33:23)
        at processTicksAndRejections (node:internal/process/task_queues:96:5)
        at connectRetry (.../shopify-non-embedded-app-template/node_modules/ngrok/index.js:29:22)
        at setEnvironmentAndReturnHost (file://.../shopify-non-embedded-app-template/next.config.mjs:43:20)
        at nextConfig (file://.../shopify-non-embedded-app-template/next.config.mjs:61:10)
        at Object.normalizeConfig (.../shopify-non-embedded-app-template/node_modules/next/server/config-shared.ts:512:10)
        at Object.loadConfig [as default] (.../shopify-non-embedded-app-template/node_modules/next/server/config.ts:682:24)
        at NextServer.loadConfig (.../shopify-non-embedded-app-template/node_modules/next/server/next.ts:132:18)
        at NextServer.prepare (.../shopify-non-embedded-app-template/node_modules/next/server/next.ts:109:20)
        at .../shopify-non-embedded-app-template/node_modules/next/cli/next-dev.ts:105:7 {
      response: <ref *1> IncomingMessage {
        _readableState: ReadableState {
          objectMode: false,
          highWaterMark: 16384,
          buffer: BufferList { head: null, tail: null, length: 0 },
          length: 0,
          pipes: [],
          flowing: false,
          ended: true,
          endEmitted: true,
          reading: false,
          constructed: true,
          sync: false,
          needReadable: false,
          emittedReadable: false,
          readableListening: true,
          resumeScheduled: false,
          errorEmitted: false,
          emitClose: true,
          autoDestroy: true,
          destroyed: true,
          errored: null,
          closed: true,
          closeEmitted: true,
          defaultEncoding: 'utf8',
          awaitDrainWriters: null,
          multiAwaitDrain: false,
          readingMore: false,
          dataEmitted: true,
          decoder: null,
          encoding: null,
          [Symbol(kPaused)]: null
        },
        _events: [Object: null prototype] {
          end: [Function: responseOnEnd],
          aborted: [Array],
          error: [Function],
          readable: [Function (anonymous)]
        },
        _eventsCount: 4,
        _maxListeners: undefined,
        socket: Socket {
          connecting: false,
          _hadError: false,
          _parent: null,
          _host: null,
          _readableState: [ReadableState],
          _events: [Object: null prototype],
          _eventsCount: 7,
          _maxListeners: undefined,
          _writableState: [WritableState],
          allowHalfOpen: false,
          _sockname: null,
          _pendingData: null,
          _pendingEncoding: '',
          server: null,
          _server: null,
          parser: null,
          _httpMessage: [ClientRequest],
          _peername: [Object],
          [Symbol(async_id_symbol)]: 327,
          [Symbol(kHandle)]: [TCP],
          [Symbol(kSetNoDelay)]: false,
          [Symbol(lastWriteQueueSize)]: 0,
          [Symbol(timeout)]: null,
          [Symbol(kBuffer)]: null,
          [Symbol(kBufferCb)]: null,
          [Symbol(kBufferGen)]: null,
          [Symbol(kCapture)]: false,
          [Symbol(kBytesRead)]: 0,
          [Symbol(kBytesWritten)]: 0,
          [Symbol(RequestTimeout)]: undefined
        },
        httpVersionMajor: 1,
        httpVersionMinor: 1,
        httpVersion: '1.1',
        complete: true,
        rawHeaders: [
          'Content-Type',
          'application/json',
          'Date',
          'Sat, 02 Jul 2022 07:49:41 GMT',
          'Content-Length',
          '105',
          'Connection',
          'close'
        ],
        rawTrailers: [],
        aborted: false,
        upgrade: false,
        url: 'http://127.0.0.1:4041/api/tunnels',
        method: null,
        statusCode: 502,
        statusMessage: 'Bad Gateway',
        client: Socket {
          connecting: false,
          _hadError: false,
          _parent: null,
          _host: null,
          _readableState: [ReadableState],
          _events: [Object: null prototype],
          _eventsCount: 7,
          _maxListeners: undefined,
          _writableState: [WritableState],
          allowHalfOpen: false,
          _sockname: null,
          _pendingData: null,
          _pendingEncoding: '',
          server: null,
          _server: null,
          parser: null,
          _httpMessage: [ClientRequest],
          _peername: [Object],
          [Symbol(async_id_symbol)]: 327,
          [Symbol(kHandle)]: [TCP],
          [Symbol(kSetNoDelay)]: false,
          [Symbol(lastWriteQueueSize)]: 0,
          [Symbol(timeout)]: null,
          [Symbol(kBuffer)]: null,
          [Symbol(kBufferCb)]: null,
          [Symbol(kBufferGen)]: null,
          [Symbol(kCapture)]: false,
          [Symbol(kBytesRead)]: 0,
          [Symbol(kBytesWritten)]: 0,
          [Symbol(RequestTimeout)]: undefined
        },
        _consuming: true,
        _dumped: false,
        req: ClientRequest {
          _events: [Object: null prototype],
          _eventsCount: 10,
          _maxListeners: undefined,
          outputData: [],
          outputSize: 0,
          writable: true,
          destroyed: false,
          _last: true,
          chunkedEncoding: false,
          shouldKeepAlive: false,
          maxRequestsOnConnectionReached: false,
          _defaultKeepAlive: true,
          useChunkedEncodingByDefault: true,
          sendDate: false,
          _removedConnection: false,
          _removedContLen: false,
          _removedTE: false,
          _contentLength: null,
          _hasBody: true,
          _trailer: '',
          finished: true,
          _headerSent: true,
          _closed: false,
          socket: [Socket],
          _header: 'POST /api/tunnels HTTP/1.1\r\n' +
            'user-agent: got (https://github.com/sindresorhus/got)\r\n' +
            'content-type: application/json\r\n' +
            'accept: application/json\r\n' +
            'content-length: 138\r\n' +
            'accept-encoding: gzip, deflate, br\r\n' +
            'Host: 127.0.0.1:4041\r\n' +
            'Connection: close\r\n' +
            '\r\n',
          _keepAliveTimeout: 0,
          _onPendingData: [Function: nop],
          agent: [Agent],
          socketPath: undefined,
          method: 'POST',
          maxHeaderSize: undefined,
          insecureHTTPParser: undefined,
          path: '/api/tunnels',
          _ended: true,
          res: [Circular *1],
          aborted: false,
          timeoutCb: null,
          upgradeOrConnect: false,
          parser: null,
          maxHeadersCount: null,
          reusedSocket: false,
          host: '127.0.0.1',
          protocol: 'http:',
          timings: [Object],
          emit: [Function (anonymous)],
          [Symbol(kCapture)]: false,
          [Symbol(kNeedDrain)]: false,
          [Symbol(corked)]: 0,
          [Symbol(kOutHeaders)]: [Object: null prototype],
          [Symbol(reentry)]: true
        },
        timings: {
          start: 1656748180990,
          socket: 1656748180990,
          lookup: 1656748180991,
          connect: 1656748180991,
          secureConnect: undefined,
          upload: 1656748180991,
          response: 1656748181098,
          end: 1656748181098,
          error: undefined,
          abort: undefined,
          phases: [Object]
        },
        emit: [Function (anonymous)],
        requestUrl: 'http://127.0.0.1:4041/api/tunnels',
        redirectUrls: [],
        request: Request {
          _readableState: [ReadableState],
          _events: [Object: null prototype],
          _eventsCount: 14,
          _maxListeners: undefined,
          _writableState: [WritableState],
          allowHalfOpen: true,
          requestInitialized: true,
          redirects: [],
          retryCount: 0,
          _progressCallbacks: [],
          write: [Function: onLockedWrite],
          end: [Function: onLockedWrite],
          options: [Object],
          requestUrl: 'http://127.0.0.1:4041/api/tunnels',
          _cannotHaveBody: false,
          _noPipe: true,
          [Symbol(kCapture)]: false,
          [Symbol(downloadedSize)]: 105,
          [Symbol(uploadedSize)]: 138,
          [Symbol(serverResponsesPiped)]: Set(0) {},
          [Symbol(stopReading)]: true,
          [Symbol(triggerRead)]: false,
          [Symbol(jobs)]: [],
          [Symbol(body)]: '{"addr":3000,"authtoken":"<REMOVED>","proto":"http","name":"<REMOVED>"}',
          [Symbol(bodySize)]: 138,
          [Symbol(cancelTimeouts)]: [Function: cancelTimeouts],
          [Symbol(unproxyEvents)]: [Function (anonymous)],
          [Symbol(request)]: [ClientRequest],
          [Symbol(originalResponse)]: [Circular *1],
          [Symbol(isFromCache)]: false,
          [Symbol(responseSize)]: 105,
          [Symbol(response)]: [Circular *1],
          [Symbol(startedReading)]: true
        },
        isFromCache: false,
        ip: '127.0.0.1',
        retryCount: 0,
        rawBody: <Buffer 7b 22 65 72 72 6f 72 5f 63 6f 64 65 22 3a 31 30 33 2c 22 73 74 61 74 75 73 5f 63 6f 64 65 22 3a 35 30 32 2c 22 6d 73 67 22 3a 22 66 61 69 6c 65 64 20 ... 55 more bytes>,
        body: '{"error_code":103,"status_code":502,"msg":"failed to start tunnel","details":{"err":"remote gone away"}}\n',
        [Symbol(kCapture)]: false,
        [Symbol(kHeaders)]: {
          'content-type': 'application/json',
          date: 'Sat, 02 Jul 2022 07:49:41 GMT',
          'content-length': '105',
          connection: 'close'
        },
        [Symbol(kHeadersCount)]: 8,
        [Symbol(kTrailers)]: null,
        [Symbol(kTrailersCount)]: 0,
        [Symbol(RequestTimeout)]: undefined
      },
      body: {
        error_code: 103,
        status_code: 502,
        msg: 'failed to start tunnel',
        details: { err: 'remote gone away' }
      }
    }
    
    

    To recreate:

    1. Clone the repo to local
    2. Create .env file with SHOPIFY_API_KEY, SHOPIFY_API_SECRET, SHOP, SCOPES, NGROK_AUTH_TOKEN, UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN
    3. Executed npm run dev

    I have checked to ensure that the Ngrok tunnel is working.

    opened by nabusman 3
  • Concerns about Redis and Middleware

    Concerns about Redis and Middleware

    Hi, firstly I want to thank you all for the amazing job done here. I personally wanted to build a non-embedded app and I just started in de middle, I am a bit lost with Shopify Docs, but I am getting ahead thanks to your repo!

    I understand that the repo is maintained frequently, due to the last month commit! But there is a matter that I want to discuss with you guys. As you do store your session storage on Redis, I do the same thing. So I was wondering if CustomeSessionStorage could be replaced by RedisSessionStorage?

    Another issue, this time addressing the middleware, could Shopify.Utils.loadCurrentSession be used to the job? Instead of going looking for cookies? Isn't that what this function is all about? Correct me if I am wrong!

    Thx for your, keep up your amazing work guys!

    opened by aimproxy 2
  • fix: partners url update mutation variable key

    fix: partners url update mutation variable key

    When cloning and running yarn dev, I get this error:

    Error: Variable $applicationUrl of type Url! was provided invalid value: {"response":{"errors":[{"message":"Variable $applicationUrl of type Url! was provided invalid value","locations":[{"line":2,"column":40}],"extensions":{"value":null,"problems":[{"path":[],"explanation":"Expected value to not be null"}]}},{"message":"Variable $redirectUrlWhitelist of type [Url]! was provided invalid value","locations":[{"line":2,"column":63}],"extensions":{"value":null,"problems":[{"path":[],"explanation":"Expected value to not be null"}]}}],"status":200,"headers":{}},"request":{"query":"\n  mutation appUpdate($apiKey: String!, $applicationUrl: Url!, $redirectUrlWhitelist: [Url]!) {\n    appUpdate(input: {apiKey: $apiKey, applicationUrl: $applicationUrl, redirectUrlWhitelist: $redirectUrlWhitelist}) {\n      userErrors {\n        message\n        field\n      }\n    }\n  }\n",
    

    Here are the mutation variable keys from the Shopify CLI Kit.

    opened by anguy95 1
  • :ambulance: Replacing localtunnel with ngrok

    :ambulance: Replacing localtunnel with ngrok

    Localtunnel has been proven to be an unreliable solution for proxying the app in dev mode. Thus it has been replaced by Ngrok. #6

    In addition to that, the App URLs in the Shopify partners dashboard will be updated automatically with help of the Shopify CLI.

    opened by carstenlebek 1
  • Redirect not working correctly when the sessions are cleared in the DB

    Redirect not working correctly when the sessions are cleared in the DB

    These lines:

    https://github.com/carstenlebek/shopify-non-embedded-app-template/blob/4d50af598424f2dc59605c4bfce8b03f7b8ce635/src/pages/_middleware.js#L15-L27

    Should be:

    const response = await fetch(
      `${process.env.HOST}/api/auth/verify-session?sessionToken=${req.cookies["shopify_app_session"]}`
    );
    if (response.status === 200) {
      return NextResponse.next();
    } else {
      if (shop) {
        return NextResponse.redirect(
          `${process.env.HOST}/api/auth/offline?shop=${shop}`
        );
      } else {
        return NextResponse.redirect(`${process.env.HOST}/login`);
      }
    }
    
    
    opened by ivorpad 1
  • 404 not found

    404 not found

    I'm getting a 404 not found error after adding the demo store url and clicking on login.

    When I logged authRoute, it fetches authRoute fine, but this error happens when it hits the callback url.

    Any idea?

    opened by zifahm 0
  • No organization exception is thrown

    No organization exception is thrown

    Good evening,

    It's my first Shopify shop project and I wanted to use this template. I followed all you guide to create it, but on yarn dev the exception NoOrgError is always thrown. Is that because I did not configure my Shopify on the right way ? Should I use Shopify Plus to have organizations ?

    opened by stephraja 0
  • Only absolute URLs are supported

    Only absolute URLs are supported

    Hi,

    I am heaving issue with your app. I am not experienced with frontend development. I forked, setup .env, ngrok etc and tried to install it. When ngrok redirect me to localhost I have this error:

    Error: CustomSessionStorage failed to store a session. Error Details: TypeError: Only absolute URLs are supported

    Any idea what is wrong? I tried to track it, but this is unknown area for me.

    Full stack trace:

    Server Error Error: CustomSessionStorage failed to store a session. Error Details: TypeError: Only absolute URLs are supported

    This error happened while generating the page. Any console logs will be displayed in the terminal window. Call Stack SessionStorageError.ShopifyError [as constructor] file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/dist/error.js (13:28) new SessionStorageError file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/dist/error.js (186:42) CustomSessionStorage. file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/dist/auth/session/storage/custom.js (27:31) step file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js (144:27) Object.throw file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js (125:57) rejected file:///home/matthew/Projects/Softonics/shopify-non-embedded-app-template/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js (116:69) processTicksAndRejections node:internal/process/task_queues (96:5)

    opened by woody41 0
Owner
Carsten Lebek
Just trying to build cool things and giving back to the community. Need someone to build a @Shopify app? Hit me up 🙌ðŸŧ
Carsten Lebek
Sample of how to use Frontegg with embedded react login-box

Getting Started with Frontegg Embedded Login-Box and React This sample is a React Login-Box embedded sample crafted with React.js. Running the sample

Frontegg samples 1 Jun 14, 2022
A demo of a Shopify site using Astro and React.

Shopify + Astro + React A demo of a Shopify site using Astro and React. Commands All commands are run from the root of the project, from a terminal: C

Cassidy Williams 98 Jan 8, 2023
This tutorial to guide you how to add react.js into shopify normal theme

Integrate-react.js-into-shopify-theme Tutorial to integrate the react.js into shopify theme https://prnt.sc/1w0rgx0 Step note: you have to run theme w

Roman Gavrilov 8 Oct 17, 2022
Builder.io Headless Page Builder example with Shopify Hydrogen

Builder.io + Shopify Hydrogen - headless visual page building example Example repo using Builder.io for drag and drop page building with Shopify hydro

Builder.io 49 Oct 30, 2022
Demo of using `@shopify/react-native-skia` with Expo for web

Expo Skia Demo Web Demo yarn -- install packages Run node copy-canvaskit-wasm.js web

Evan Bacon 15 Dec 2, 2022
A serverless Notion-powered blog in React

A serverless Notion-powered blog in React I'm trying to build for fun. If I had to pick a name for this, it'd be COKE.

Arth Tyagi 29 Nov 15, 2022
Aviasales React-Redux-MobX- - A quick start Redux + TypeScript Create React App template

A quick start Redux + TypeScript Create React App template An opinionated quick

null 0 Mar 14, 2022
Create guided tours in your apps

React Joyride Create awesome tours for your app! Showcase your app to new users or explain functionality of new features. It uses react-floater for po

Gil Barbara 5.1k Dec 30, 2022
HTML meta tags for React-based apps. Works for both client- and server-side rendering, and has a strict but flexible API.

React Document Meta HTML meta tags for React-based apps. Works for both client- and server-side rendering, and has a strict but flexible API. Built wi

kodyl 320 Nov 18, 2022
Build blazing fast, modern apps and websites with React

Gatsby v2 ⚛ïļ ?? ?? Fast in every way that matters Gatsby is a free and open source framework based on React that helps developers build blazing fast w

Gatsby 54k Jan 7, 2023
An all in one preset for writing Preact apps with the vite bundler.

@preact/preset-vite An all in one preset for writing Preact apps with the vite bundler. Features: Sets up Hot Module Replacement via prefresh Enables

Preact 180 Dec 29, 2022
This repository contains different React components, hooks, apps and libraries that are used in different projects here at NaN Labs.

This repository contains different React components, hooks, apps and libraries that are used in different projects here at NaN Labs.

NaN Labs 9 Nov 18, 2022
Simple Light is a free landing page template built on top of TailwindCSS and fully coded in React. Made by

Simple Light is a free landing page template built on top of TailwindCSS and fully coded in React. Made by

Cruip 1.7k Dec 27, 2022
Small React three fiber multiplayer template

R3F.Multiplayer React three fiber, socket.io boilerplate multiplayer server and client ?? Getting started Getting started To quickly get started Clone

Or Fleisher 81 Dec 24, 2022
Ethereum nft marketplace template using react

ethereum-marketplace-template ⭐ïļ Star us If this boilerplate helps you build Ethereum dapps faster - please star this project, every star makes us ver

Ethereum Boilerplates 73 Dec 17, 2022
React.js, Typescript, Effector, Storybook, Eslint+Prettier, husky template

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: ya

Vladislav 1 Apr 5, 2022
Reactivated.app is an open-source app that scans your JS dependencies every 4 hours and generates cool dashboards

Reactivated.app is an open-source app that scans your JS dependencies every 4 hours and generates cool dashboards

Premier Octet 66 Jul 20, 2022
Insomnia-react-app - A react app that will help you relax and sleep better

insomnia - a sleep inducer app An app that will help you relax and sleep better.

null 8 Dec 27, 2022
Solana-base-app - Solana-base-app is for Solana beginners to get them up and running fast

solana-base-app solana-base-app is for Solana beginners to get them up and runni

UjjwalGupta49 33 Dec 27, 2022