├── .env.sample ├── .gitignore ├── README.md ├── components ├── App │ ├── App.graphql │ ├── App.js │ ├── App.module.css │ └── index.js ├── Button │ ├── Button.js │ ├── Button.module.css │ └── index.js ├── Category │ ├── Category.graphql │ ├── Category.js │ ├── Category.module.css │ └── index.js ├── Home │ ├── Home.js │ ├── Home.module.css │ └── index.js ├── Price │ ├── Price.js │ ├── _price_range.graphql │ └── index.js ├── Product │ ├── Product.graphql │ ├── Product.js │ ├── Product.module.css │ └── index.js └── Products │ ├── Products.graphql │ ├── Products.js │ ├── Products.module.css │ └── index.js ├── jsconfig.json ├── lib ├── apollo-client.js ├── express-middleware.js └── resolve-image.js ├── next.config.js ├── package.json ├── pages ├── _app.js ├── _url-resolver.js ├── api │ └── proxy.js └── index.js ├── public ├── favicon.ico └── static │ └── logo.png ├── styles └── global.css └── yarn.lock /.env.sample: -------------------------------------------------------------------------------- 1 | MAGENTO_URL="https://venia.magento.com/" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .next 3 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How-to Magento with Next.js 2 | ## vol.1 Getting Started 3 | 4 | [![Reacticon](https://img.youtube.com/vi/FdJAuEGVg9w/0.jpg)](https://www.youtube.com/embed/FdJAuEGVg9w) 5 | 6 | 7 | __📣 Note: This sample was developed before Next.js 10 and Next.js Commerce. (Update in progress)__ 8 | 9 | Next.js is a minimalistic JAMstack framework; most of it is invisible to you (the developer). However, it is extensible when you need to customize and bring other third-party libraries. 10 | 11 | ### What you get out of the box with Next.js 12 | - Pre-rendering. Static generated and server-side rendered. 13 | - Zero Configuration. Auto code splitting, file-system based routing, and universal rendering. 14 | - Static Exporting. It just happens. One command to build. 15 | - Fully Extensible. Complete control over Babel and Webpack. Next-plugins. 16 | - CSS-in-JS. Next comes with styled-jsx included, but it also works with other styling solutions. 17 | - Ready for Production. Optimized for a smaller build size. 18 | 19 | **⚠️ This is proof-of-concept for a Next.js project using data from Magento.** 20 | 21 | ### Getting Started 22 | 23 | ☝️ Create a new `.env` file based on `.env.sample`, and change the value of `MAGENTO_URL` to point to your Magento instance. 24 | 25 | 👌 Install dependencies by running `npm install` or `yarn install` 26 | 27 | 28 | ### Development Mode 29 | 30 | 👌 Run `npm run dev` or `yarn dev` to start the application on development mode, and visit https://localhost:3000 31 | 32 | 33 | ### Production Mode 34 | 35 | ☝️ Run `npm run build` or `yarn build` 36 | 37 | 👌 Run `npm run start` or `yarn start`, and visit https://localhost:3000 38 | -------------------------------------------------------------------------------- /components/App/App.graphql: -------------------------------------------------------------------------------- 1 | query App { 2 | storeConfig { 3 | id 4 | category_url_suffix # One of those things. 🤷‍♂️ 5 | default_title 6 | base_media_url 7 | logo_alt 8 | header_logo_src 9 | copyright 10 | } 11 | 12 | categoryList { 13 | id 14 | children { 15 | id 16 | url_key 17 | name 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /components/App/App.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@apollo/client' 2 | import React from 'react' 3 | import styles from './App.module.css' 4 | import APP_QUERY from './App.graphql' 5 | import Link from 'next/link' 6 | import NextNprogress from 'nextjs-progressbar' 7 | import Head from 'next/head' 8 | import { resolveImage } from '~/lib/resolve-image' 9 | 10 | export const App = ({ children }) => { 11 | const { data } = useQuery(APP_QUERY) 12 | 13 | const store = data?.storeConfig 14 | 15 | const categoryUrlSuffix = store?.category_url_suffix ?? '' 16 | 17 | const categories = data?.categoryList[0].children 18 | 19 | return ( 20 | 21 | 22 | {store?.default_title} 23 | 24 | 25 |
26 | 32 | 33 |
34 | 35 | 36 | {store?.logo_alt 46 | 47 | 48 | 49 | 69 |
70 | 71 |
{children}
72 | 73 | {store?.copyright && ( 74 | 77 | )} 78 |
79 |
80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /components/App/App.module.css: -------------------------------------------------------------------------------- 1 | .app { 2 | display: grid; 3 | grid-gap: 20px; 4 | grid-auto-rows: max-content; 5 | } 6 | 7 | .header { 8 | border-bottom: 1px solid #eee; 9 | padding: 10px; 10 | background-color: #f6f6f6; 11 | display: grid; 12 | grid-template-columns: 1fr; 13 | grid-gap: 16px; 14 | align-items: center; 15 | } 16 | .header img { 17 | max-height: 40px; 18 | max-width: 400px; 19 | } 20 | 21 | .categoriesWrapper { 22 | overflow-x: auto; 23 | max-width: 100%; 24 | -ms-overflow-style: none; /* IE and Edge */ 25 | scrollbar-width: none; /* Firefox */ 26 | text-align: center; 27 | } 28 | 29 | .categoriesWrapper::-webkit-scrollbar { 30 | display: none; 31 | } 32 | 33 | .categories { 34 | display: inline-grid; 35 | grid-auto-flow: column; 36 | grid-auto-columns: max-content; 37 | grid-gap: 20px; 38 | } 39 | 40 | .categories li a { 41 | color: inherit; 42 | text-decoration: none; 43 | } 44 | 45 | .categories li a:hover { 46 | text-decoration: underline; 47 | } 48 | 49 | .content { 50 | padding: 0 10px; 51 | } 52 | 53 | .footer { 54 | border-top: 1px solid #eee; 55 | padding: 40px 20px; 56 | text-align: center; 57 | color: #666; 58 | font-size: 12px; 59 | } 60 | 61 | /* Tablet */ 62 | @media (min-width: 600px) { 63 | .header { 64 | grid-template-columns: auto 1fr; 65 | grid-gap: 20px; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /components/App/index.js: -------------------------------------------------------------------------------- 1 | export * from './App' 2 | export { App as default } from './App' 3 | -------------------------------------------------------------------------------- /components/Button/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './Button.module.css' 3 | 4 | export const Button = ({ ...props }) => { 5 | return 51 | 52 | 53 | {product.description?.html && ( 54 |
58 | )} 59 |
60 | 61 | 62 | 63 | 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /components/Product/Product.module.css: -------------------------------------------------------------------------------- 1 | .product { 2 | display: grid; 3 | grid-gap: 20px; 4 | grid-template-columns: 1fr; 5 | } 6 | 7 | .gallery { 8 | overflow-x: auto; 9 | scroll-snap-type: x mandatory; 10 | display: flex; 11 | } 12 | 13 | .gallery img { 14 | scroll-snap-align: center; 15 | width: 100%; 16 | height: auto; 17 | } 18 | 19 | .details { 20 | display: grid; 21 | grid-gap: 16px; 22 | grid-auto-rows: max-content; 23 | padding: 0 10px; 24 | } 25 | 26 | .details > h2 { 27 | font-size: 23px; 28 | } 29 | 30 | .sku { 31 | color: #999; 32 | } 33 | 34 | .buttonWrapper { 35 | padding: 10px 0; 36 | } 37 | 38 | .buttonWrapper button { 39 | width: 100%; 40 | } 41 | 42 | .description { 43 | font-size: 14px; 44 | line-height: 1.5; 45 | } 46 | 47 | .description p { 48 | margin-bottom: 20px; 49 | } 50 | 51 | .description ul, 52 | .description ol { 53 | list-style-position: inside; 54 | } 55 | 56 | .description ul { 57 | list-style-type: disc; 58 | } 59 | 60 | .description ol { 61 | list-style-type: decimal; 62 | } 63 | 64 | .description li { 65 | margin-bottom: 10px; 66 | margin-left: 6px; 67 | } 68 | 69 | /* Tablet */ 70 | @media (min-width: 600px) { 71 | .product { 72 | grid-template-columns: 1fr 1fr; 73 | grid-gap: 20px; 74 | align-items: center; 75 | } 76 | 77 | .gallery { 78 | overflow-x: auto; 79 | scroll-snap-type: unset; 80 | display: grid; 81 | grid-auto-rows: max-content; 82 | grid-gap: 10px; 83 | } 84 | 85 | .content { 86 | align-self: flex-start; 87 | } 88 | 89 | .detailsWrapper { 90 | position: sticky; 91 | top: 20px; 92 | min-height: 90vh; 93 | display: flex; 94 | justify-content: center; 95 | align-items: center; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /components/Product/index.js: -------------------------------------------------------------------------------- 1 | export * from './Product' 2 | export { Product as default } from './Product' 3 | -------------------------------------------------------------------------------- /components/Products/Products.graphql: -------------------------------------------------------------------------------- 1 | #import "~/components/Price/_price_range.graphql" 2 | 3 | query ProductsQuery( 4 | $search: String 5 | $filters: ProductAttributeFilterInput 6 | $pageSize: Int = 12 7 | $currentPage: Int = 1 8 | ) { 9 | storeConfig { 10 | id 11 | product_url_suffix # One of those things. 🤷‍♂️ 12 | } 13 | 14 | products( 15 | search: $search 16 | filter: $filters 17 | pageSize: $pageSize 18 | currentPage: $currentPage 19 | ) { 20 | page_info { 21 | current_page 22 | total_pages 23 | } 24 | 25 | items { 26 | id 27 | url_key 28 | name 29 | 30 | thumbnail { 31 | id: url 32 | url 33 | label 34 | } 35 | 36 | price_range { 37 | ...price_range 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /components/Products/Products.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { useQuery } from '@apollo/client' 3 | import PRODUCTS_QUERY from './Products.graphql' 4 | import styles from './Products.module.css' 5 | import { resolveImage } from '~/lib/resolve-image' 6 | import Link from 'next/link' 7 | import Price from '~/components/Price' 8 | import Button from '~/components/Button' 9 | 10 | export const Products = ({ search, filters }) => { 11 | const { loading, data, fetchMore } = useQuery(PRODUCTS_QUERY, { 12 | variables: { search, filters }, 13 | notifyOnNetworkStatusChange: true, 14 | }) 15 | 16 | const page = data?.products.page_info 17 | 18 | const products = data?.products.items || [] 19 | 20 | const productUrlSuffix = data?.storeConfig.product_url_suffix ?? '' 21 | 22 | const handleFetchMore = useCallback(() => { 23 | if (loading || !page || page.current_page === page.total_pages) return 24 | 25 | fetchMore({ 26 | variables: { 27 | currentPage: page.current_page + 1, // next page 28 | }, 29 | updateQuery: (prev, { fetchMoreResult }) => { 30 | if (!fetchMoreResult) return prev 31 | 32 | return { 33 | ...prev, 34 | products: { 35 | ...prev.products, 36 | ...fetchMoreResult.products, 37 | items: [...prev.products.items, ...fetchMoreResult.products.items], 38 | }, 39 | } 40 | }, 41 | }) 42 | }, [loading, page, fetchMore]) 43 | 44 | if (loading && !data) return
⌚️ Loading...
45 | 46 | if (products.length === 0) return
🧐 No products found.
47 | 48 | return ( 49 |
50 |
51 | {products.map((product) => ( 52 | 63 | 64 |
65 | 66 | {product.thumbnail.label} 77 | 78 | 79 | 80 | {product.name} 81 | 82 | 83 |
84 |
85 | 86 | ))} 87 |
88 | 89 | {page && page.current_page !== page.total_pages && ( 90 | 93 | )} 94 |
95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /components/Products/Products.module.css: -------------------------------------------------------------------------------- 1 | .products { 2 | display: grid; 3 | grid-auto-rows: max-content; 4 | grid-gap: 40px; 5 | } 6 | 7 | .productsList { 8 | display: grid; 9 | grid-template-columns: 1fr; 10 | grid-gap: 20px; 11 | } 12 | 13 | .productsList > a { 14 | text-decoration: none; 15 | color: inherit; 16 | } 17 | 18 | .productItem { 19 | display: grid; 20 | grid-auto-rows: max-content; 21 | background-color: rgba(34, 34, 34, 0.05); 22 | border-radius: 20px; 23 | overflow: hidden; 24 | } 25 | 26 | .productWrapper { 27 | position: relative; 28 | line-height: 0; 29 | } 30 | 31 | .productWrapper::after { 32 | pointer-events: none; 33 | content: ''; 34 | position: absolute; 35 | top: 0px; 36 | left: 0px; 37 | width: 100%; 38 | height: 100%; 39 | z-index: 1; 40 | box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 100px inset; 41 | cursor: inherit; 42 | } 43 | 44 | .productImage { 45 | width: 100%; 46 | height: auto; 47 | object-fit: cover; 48 | } 49 | 50 | .productName { 51 | padding: 20px; 52 | font-size: 14px; 53 | display: grid; 54 | grid-gap: 10px; 55 | } 56 | 57 | .productName > span { 58 | opacity: 0.7; 59 | font-size: 0.9em; 60 | } 61 | 62 | /* Tablet */ 63 | @media (min-width: 600px) { 64 | .productsList { 65 | grid-template-columns: 1fr 1fr; 66 | } 67 | } 68 | 69 | /* Desktop */ 70 | @media (min-width: 992px) { 71 | .productsList { 72 | grid-template-columns: 1fr 1fr 1fr; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /components/Products/index.js: -------------------------------------------------------------------------------- 1 | export * from './Products' 2 | export { Products as default } from './Products' 3 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "~/*": [ 6 | "./*" 7 | ] 8 | } 9 | }, 10 | "exclude": [ 11 | "./node_modules/*", 12 | ] 13 | } -------------------------------------------------------------------------------- /lib/apollo-client.js: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client' 3 | import { concatPagination } from '@apollo/client/utilities' 4 | 5 | /** 6 | * Polyfill Global Variables in Server 7 | */ 8 | if (!process.browser) { 9 | global.URL = require('url').URL 10 | } 11 | 12 | let apolloClient 13 | 14 | function createApolloClient() { 15 | const uri = process.browser 16 | ? new URL('/graphql', location.href) 17 | : new URL('/graphql', process.env.MAGENTO_URL).href 18 | 19 | return new ApolloClient({ 20 | ssrMode: !process.browser, 21 | credentials: 'include', 22 | link: new HttpLink({ 23 | uri, 24 | credentials: 'include', // Additional fetch() options like `credentials` or `headers` 25 | }), 26 | cache: new InMemoryCache({ 27 | typePolicies: { 28 | Query: { 29 | fields: { 30 | allPosts: concatPagination(), 31 | }, 32 | }, 33 | }, 34 | }), 35 | }) 36 | } 37 | 38 | export function initializeApollo(initialState = null) { 39 | const _apolloClient = apolloClient ?? createApolloClient() 40 | 41 | // If your page has Next.js data fetching methods that use Apollo Client, the initial state 42 | // gets hydrated here 43 | if (initialState) { 44 | // Get existing cache, loaded during client side data fetching 45 | const existingCache = _apolloClient.extract() 46 | // Restore the cache using the data passed from getStaticProps/getServerSideProps 47 | // combined with the existing cached data 48 | _apolloClient.cache.restore({ ...existingCache, ...initialState }) 49 | } 50 | // For SSG and SSR always create a new Apollo Client 51 | if (typeof window === 'undefined') return _apolloClient 52 | // Create the Apollo Client once in the client 53 | if (!apolloClient) apolloClient = _apolloClient 54 | 55 | return _apolloClient 56 | } 57 | 58 | export function useApollo(initialState) { 59 | const store = useMemo(() => initializeApollo(initialState), [initialState]) 60 | return store 61 | } 62 | -------------------------------------------------------------------------------- /lib/express-middleware.js: -------------------------------------------------------------------------------- 1 | // Helper method to wait for a middleware to execute before continuing 2 | // And to throw an error when an error happens in a middleware 3 | export const runMiddleware = (req, res, fn) => { 4 | return new Promise((resolve, reject) => { 5 | fn(req, res, (result) => { 6 | if (result instanceof Error) { 7 | return reject(result) 8 | } 9 | 10 | return resolve(result) 11 | }) 12 | }) 13 | } -------------------------------------------------------------------------------- /lib/resolve-image.js: -------------------------------------------------------------------------------- 1 | if (!process.browser) { 2 | const { URL } = require('url') 3 | global.URL = URL 4 | } 5 | 6 | export const resolveImage = (url) => { 7 | if (!url) return undefined 8 | 9 | const { pathname } = new URL(url) 10 | 11 | if (pathname) { 12 | return `/store${pathname}` 13 | } else { 14 | return url 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { URL } = require('url') 3 | 4 | module.exports = { 5 | webpack: (config) => { 6 | config.module.rules.push({ 7 | test: /\.(graphql|gql)$/, 8 | loader: 'graphql-tag/loader', 9 | }) 10 | 11 | config.resolve.alias = { 12 | ...config.resolve.alias, 13 | '~': path.resolve(__dirname), 14 | } 15 | 16 | return config 17 | }, 18 | async rewrites() { 19 | return [ 20 | /** 21 | * Rewrite /graphql requests to Magento 22 | */ 23 | { 24 | source: '/graphql/:pathname*', 25 | destination: new URL('graphql', process.env.MAGENTO_URL).href, 26 | }, 27 | 28 | /** 29 | * Sample of how to use APIs to Proxy Images 30 | */ 31 | { 32 | source: '/store/:pathname*', 33 | destination: '/api/proxy', 34 | }, 35 | 36 | /** 37 | * URlResolver 🙌 38 | */ 39 | { 40 | source: '/:pathname*', 41 | destination: '/_url-resolver', 42 | }, 43 | ] 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magento-nextjs-starter", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Carlos A. Cabrera @fnhipster", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "next dev", 9 | "build": "next build", 10 | "start": "next start" 11 | }, 12 | "dependencies": { 13 | "@apollo/client": "^3.2.3", 14 | "graphql": "^15.3.0", 15 | "http-proxy-middleware": "^1.0.5", 16 | "next": "^9.5.5", 17 | "nextjs-progressbar": "^0.0.6", 18 | "react": "^16.13.1", 19 | "react-dom": "^16.13.1", 20 | "reset-css": "^5.0.1", 21 | "use-debounce": "^5.0.1" 22 | }, 23 | "devDependencies": { 24 | "graphql-tag": "^2.11.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import { ApolloProvider } from '@apollo/client' 2 | import { useApollo } from '~/lib/apollo-client' 3 | import App from '~/components/App' 4 | import 'reset-css' 5 | import '~/styles/global.css' 6 | 7 | export default function NextApp({ Component, pageProps }) { 8 | const apolloClient = useApollo(pageProps.initialApolloState) 9 | 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /pages/_url-resolver.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Error from 'next/error' 3 | import { gql } from '@apollo/client' 4 | import { initializeApollo } from '~/lib/apollo-client' 5 | 6 | import APP_QUERY from '~/components/App/App.graphql' 7 | import PRODUCTS_QUERY from '~/components/Products/Products.graphql' 8 | 9 | import Category from '~/components/Category' 10 | import CATEGORY_QUERY from '~/components/Category/Category.graphql' 11 | 12 | import Product from '~/components/Product' 13 | import PRODUCT_QUERY from '~/components/Product/Product.graphql' 14 | 15 | const CONTENT_TYPE = { 16 | CMS_PAGE: 'CMS_PAGE', 17 | CATEGORY: 'CATEGORY', 18 | PRODUCT: 'PRODUCT', 19 | NOT_FOUND: '404', 20 | } 21 | 22 | const URLResolver = ({ type, urlKey }) => { 23 | if (type === CONTENT_TYPE.CMS_PAGE) { 24 | return
🥴 "CMS_PAGE" is not implemented in this sample.
25 | } 26 | 27 | if (type === CONTENT_TYPE.CATEGORY) { 28 | return 29 | } 30 | 31 | if (type === CONTENT_TYPE.PRODUCT) { 32 | return 33 | } 34 | 35 | return 36 | } 37 | 38 | URLResolver.getInitialProps = async ({ req, res, query, asPath }) => { 39 | res?.setHeader('cache-control', 's-maxage=1, stale-while-revalidate') 40 | 41 | const apolloClient = initializeApollo() 42 | 43 | const pathname = query?.pathname.join('/') 44 | 45 | const urlKey = query?.pathname?.pop().split('.')?.shift() || '' 46 | 47 | /** If a type has been provided then return the props and render the Component ... */ 48 | if (query.type) { 49 | return { type: query.type, urlKey } 50 | } 51 | 52 | /** ... if not, let's resolver the URL ... */ 53 | const { data } = await apolloClient.query({ 54 | query: gql` 55 | query UrlResolver($url: String!) { 56 | urlResolver(url: $url) { 57 | id 58 | type 59 | } 60 | } 61 | `, 62 | variables: { 63 | url: pathname, 64 | }, 65 | }) 66 | 67 | /** ... if not found, return 404 ... */ 68 | if (!data?.urlResolver) { 69 | if (res) res.statusCode = 404 70 | return { type: '404', pathname } 71 | } 72 | 73 | const { type, id } = data.urlResolver 74 | 75 | /** ... if the request is done by the server, then let's load the data in cache of SSR goodness ... */ 76 | if (req) { 77 | await apolloClient.query({ query: APP_QUERY }) // Preload App Data 78 | 79 | switch (type) { 80 | case CONTENT_TYPE.CMS_PAGE: 81 | // Not implemented... 82 | break 83 | case CONTENT_TYPE.CATEGORY: 84 | const { data } = await apolloClient.query({ 85 | query: CATEGORY_QUERY, 86 | variables: { filters: { url_key: { eq: urlKey } } }, 87 | }) 88 | 89 | /** If the category is set to show products, then load those products as well */ 90 | if (/PRODUCTS/.test(data.categoryList[0].display_mode)) { 91 | await apolloClient.query({ 92 | query: PRODUCTS_QUERY, 93 | variables: { filters: { category_id: { eq: id } } }, 94 | }) 95 | } 96 | break 97 | case CONTENT_TYPE.PRODUCT: 98 | await apolloClient.query({ 99 | query: PRODUCT_QUERY, 100 | variables: { filters: { url_key: { eq: urlKey } } }, 101 | }) 102 | break 103 | default: 104 | break 105 | } 106 | } 107 | 108 | /** Return Props */ 109 | return { 110 | type, 111 | urlKey, 112 | initialApolloState: apolloClient.cache.extract(), // load cached data from queries above into the initial state of the app 113 | } 114 | } 115 | 116 | export default URLResolver 117 | -------------------------------------------------------------------------------- /pages/api/proxy.js: -------------------------------------------------------------------------------- 1 | import { createProxyMiddleware } from 'http-proxy-middleware' 2 | import { URL } from 'url' 3 | import { runMiddleware } from '~/lib/express-middleware' 4 | 5 | const magentoProxyApi = async (req, res) => { 6 | const target = new URL(process.env.MAGENTO_URL).href 7 | 8 | await runMiddleware( 9 | req, 10 | res, 11 | createProxyMiddleware({ 12 | target, 13 | changeOrigin: true, 14 | secure: false, 15 | logLevel: 'error', 16 | pathRewrite: { 17 | '^/store': '/', // remove path 18 | }, 19 | }) 20 | ) 21 | } 22 | 23 | export default magentoProxyApi 24 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Home from '~/components/Home' 3 | import { initializeApollo } from '~/lib/apollo-client' 4 | import APP_QUERY from '~/components/App/App.graphql' 5 | import PRODUCTS_QUERY from '~/components/Products/Products.graphql' 6 | 7 | const HomePage = () => { 8 | return 9 | } 10 | 11 | export const getStaticProps = async () => { 12 | const apolloClient = initializeApollo() 13 | 14 | await apolloClient.query({ 15 | query: APP_QUERY, 16 | }) 17 | 18 | await apolloClient.query({ 19 | query: PRODUCTS_QUERY, 20 | variables: { search: '' }, 21 | }) 22 | 23 | return { 24 | props: { 25 | initialApolloState: apolloClient.cache.extract(), 26 | }, 27 | revalidate: 1, 28 | } 29 | } 30 | 31 | export default HomePage 32 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fnhipster/magento-with-next-sample/6b445babadbc20e1b0dc76ab5ea1c26d11f8860b/public/favicon.ico -------------------------------------------------------------------------------- /public/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fnhipster/magento-with-next-sample/6b445babadbc20e1b0dc76ab5ea1c26d11f8860b/public/static/logo.png -------------------------------------------------------------------------------- /styles/global.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0 auto; 3 | max-width: 1600px; 4 | width: 100%; 5 | font-family: 'Lucida Sans Unicode', 'Lucida Grande', sans-serif; 6 | font-size: 14px; 7 | } 8 | --------------------------------------------------------------------------------