i can't get urql subscriptions to work with NextJS because of imports problem.
Basically i'm using this graphql-ws lib that is recommended in urql docs, that needs ws implementation library (eg: 'ws'). And when i import WebSocket from 'ws' i get this error: Module not found: Can't resolve 'net'
import { createClient, defaultExchanges, subscriptionExchange, Client } from 'urql';
import { createClient as createWSClient } from 'graphql-ws';
import WebSocket from 'ws'; // <-- This causes the error
export const createUrqlClient = (): Client => {
const wsClient = createWSClient({
url: 'ws://xxx/graphql',
webSocketImpl: WebSocket,
});
const client = createClient({
url: 'http://xxx/graphql',
exchanges: [
...defaultExchanges,
subscriptionExchange({
forwardSubscription: operation => ({
subscribe: sink => ({
unsubscribe: wsClient.subscribe(operation, sink),
}),
}),
}),
],
});
return client;
};
I tried nextjs dynamic import and both of these didn't work as well:
const WebSocket = dynamic(() => import('ws'), { ssr: false });
const WebSocket = dynamic(() => import('ws').then(module => module.default), { ssr: false });
I also tried to alter webpack config in next.config.js to not bundle these libs at all:
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
child_process: false,
process: false,
fs: false,
util: false,
http: false,
https: false,
tls: false,
net: false,
crypto: false,
path: false,
os: false,
stream: false,
zlib: false,
querystring: false,
events: false,
'utf-8-validate': false,
bufferutil: false,
};
}
return config;
},
but then i get these errors:
./node_modules/ws/lib/validation.js
Module not found: Can't resolve 'utf-8-validate' in '/home/danko/app/node_modules/ws/lib'
warn - ./node_modules/ws/lib/buffer-util.js
Module not found: Can't resolve 'bufferutil' in '/home/danko/app/node_modules/ws/lib'
if i add 'utf-8-validate': false and bufferutil: false to the cfg as well i get this err:
TypeError: Class extends value undefined is not a constructor or null
So basically nothing works properly then as you can see...
How hard can this be, i can't be the only person that uses urql subscriptions with nextjs, hope somebody can help me with this. Thanks!
Basically as i thought, impl was not needed because native html5 websocket can be used, problem was trash nextjs with it's server side thing.
I pretty much don't use that exchange when typeof window !== 'undefined'
this is the working code:
import { createClient, dedupExchange, cacheExchange, subscriptionExchange, Client, Exchange } from 'urql';
import { multipartFetchExchange } from '#urql/exchange-multipart-fetch';
import { createClient as createWSClient } from 'graphql-ws';
export const createUrqlClient = (): Client => {
const graphqlEndpoint = process.env!.NEXT_PUBLIC_GRAPHQL_ENDPOINT as string;
const graphqlWebsocketEndpoint = process.env!.NEXT_PUBLIC_GRAPHQL_WS_ENDPOINT as string;
let exchanges: Exchange[] | undefined = [dedupExchange, cacheExchange, multipartFetchExchange];
if (typeof window !== 'undefined') {
const wsClient = createWSClient({
url: graphqlWebsocketEndpoint,
});
const subExchange = subscriptionExchange({
forwardSubscription: operation => ({
subscribe: sink => ({
unsubscribe: wsClient.subscribe(operation, sink),
}),
}),
});
exchanges.push(subExchange);
}
const client = createClient({
url: graphqlEndpoint,
requestPolicy: 'cache-and-network',
exchanges,
fetchOptions: () => ({
credentials: 'include',
}),
});
return client;
};
#dankobgd
I had the same problem and your answer helped me.
I simplified a bit and it's working
export const client = (): Client => {
let exchanges: Exchange[] = [...defaultExchanges]
if (typeof window !== 'undefined') {
const wsClient = createWSClient({
url: wsUrl,
})
const subExchange = subscriptionExchange({
forwardSubscription: (operation) => ({
subscribe: (sink) => ({
unsubscribe: wsClient.subscribe(operation, sink),
}),
}),
})
exchanges.push(subExchange)
}
return createClient({
url,
exchanges,
})
}
Related
It's been some days I'm trying to fix this problem but without success... I'm trying tu use next-i18next to get some translations keys coming from a CMS. We are in SSR, that why I use getServersSideProps. But got the ._nextI18Next.userConfig.use[0]returned fromgetServerSideProps` in "/" error. Do you guys got some ideas ? Am I on a bad way ? Thanks a lot for your time and your help.
Here is my next-i18next.config.js :
/* eslint-disable #typescript-eslint/no-var-requires */
const i18nextHttpBackend = require('i18next-http-backend')
const i18nextBrowserLanguageDetector = require('i18next-browser-languagedetector')
const axios = require('axios')
module.exports = {
i18n: {
defaultLocale: 'default',
locales: ['default', 'gb', 'fr']
},
use: [i18nextHttpBackend, i18nextBrowserLanguageDetector],
debug: true,
fallbackLng: 'fr',
interpolation: {
format: (value, format, lng) => {
if (value instanceof Date) {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
}
return value
}
},
backend: {
loadPath: lng => {
axios.get(`https://my_api_url/${lng}/formatted-transkeys`).then(res => {
return JSON.stringify(res.data)
})
}
},
serializeConfig: false
}
My next.config.js :
/* eslint-disable #typescript-eslint/no-var-requires */
const { i18n } = require('./next-i18next.config')
const nextConfig = {
distDir: 'dist',
reactStrictMode: false,
i18n,
trailingSlash: true,
images: {
domains: ['d1m7xnn75ypr6t.cloudfront.net']
}
}
module.exports = nextConfig
And my index.tsx :
interface i18nProps {
locale: string
}
export const getServerSideProps = async (props: i18nProps) => {
const result = await getHomePageData(props.locale)
const data = result.data
return {
props: {
data: data,
...(await serverSideTranslations(props.locale, ['search']))
}
}
}
export default Homepage
Trying to be as clear as I can, ask me everything you need.
Thanks,
R.
try
const i18nextHttpBackend = require('i18next-http-backend/cjs')
instead of
const i18nextHttpBackend = require('i18next-http-backend')
and you need to pass the config here:
import nextI18NextConfig from '../next-i18next.config.js';
// ...
...(await serverSideTranslations(props.locale, ['search'], nextI18NextConfig))
Like described here: https://github.com/i18next/next-i18next#unserialisable-configs
compare it also with this example: https://github.com/i18next/i18next-http-backend/tree/master/example/next
I don't understand these errors when I export as production npm run build , but when I test npm run dev it works just fine. I use getStaticProps and getStaticPath fetch from an API route.
First when I npm run build
FetchError: invalid json response body at https://main-website-next.vercel.app/api/products reason: Unexpected token T in JSON at position
0
at D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:272:32
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async getStaticPaths (D:\zummon\Main Website\main-website-next\.next\server\pages\product\[slug].js:1324:18)
at async buildStaticPaths (D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:16:80)
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:26:612
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\tracer.js:1:1441 {
type: 'invalid-json'
}
\pages\product\[slug]
import { assetPrefix } from '../../next.config'
export default function Page(){...}
export const getStaticProps = async ({ params: { slug }, locale }) => {
const res = await fetch(`${assetPrefix}/api/products/${slug}`)
const result = await res.json()
const data = result.filter(item => item.locale === locale)[0]
const { title, keywords, description } = data
return {
props: {
data,
description,
keywords,
title
}
}
}
export const getStaticPaths = async () => {
const res = await fetch(`${assetPrefix}/api/products`)
const result = await res.json()
const paths = result.map(({ slug, locale }) => ({ params: { slug: slug }, locale }))
return {
fallback: true,
paths,
}
}
next.config.js
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
assetPrefix: isProd ? 'https://main-website-next.vercel.app' : 'http://localhost:3000',
i18n: {
localeDetection: false,
locales: ['en', 'th'],
defaultLocale: 'en',
}
}
API routes
// pages/api/products/index.js
import data from '../../../data/products'
export default (req, res) => {
res.status(200).json(data)
}
// pages/api/products/[slug].js
import db from '../../../data/products'
export default ({ query: { slug } }, res) => {
const data = db.filter(item => item.slug === slug)
if (data.length > 0) {
res.status(200).json(data)
} else {
res.status(404).json({ message: `${slug} not found` })
}
}
// ../../../data/products (data source)
module.exports = [
{ locale: "en", slug: "google-sheets-combine-your-cashflow",
title: "Combine your cashflow",
keywords: ["Google Sheets","accounting"],
description: "...",
},
...
]
Second when I remove the production domain, I run npm run build but still get the error like
TypeError: Only absolute URLs are supported
at getNodeRequestOptions (D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1305:9)
at D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1410:19
at new Promise (<anonymous>)
at fetch (D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1407:9)
at getStaticPaths (D:\zummon\Main Website\main-website-next\.next\server\pages\[slug].js:938:21)
at buildStaticPaths (D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:16:86)
at D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:26:618
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\tracer.js:1:1441 {
type: 'TypeError'
}
My next.config.js after remove
const isProd = process.env.NODE_ENV === 'production'
module.exports = { //remove
assetPrefix: isProd ? '' : 'http://localhost:3000',
i18n: {
localeDetection: false,
locales: ['en', 'th'],
defaultLocale: 'en',
}
}
My package.json when I npm run build script
{
"name": "main-website-next",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "next start"
},
"dependencies": {
"next": "10.0.6",
"react": "17.0.1",
"react-dom": "17.0.1"
}
}
You should not call an internal API route inside getStaticProps. Instead, you can safely use your API logic directly in getStaticProps/getStaticPaths. These only happen server-side so you can write server-side code directly.
As getStaticProps runs only on the server-side, it will never run on
the client-side. It won’t even be included in the JS bundle for the
browser, so you can write direct database queries without them being
sent to browsers.
This means that instead of fetching an API route from
getStaticProps (that itself fetches data from an external source),
you can write the server-side code directly in getStaticProps.
Furthermore, your API routes are not available during build-time, as the server has not been started at that point.
Here's a small refactor of your code to address the issue.
// /pages/product/[slug]
import db from '../../../data/products'
// Remaining code..
export const getStaticProps = async ({ params: { slug }, locale }) => {
const result = db.filter(item => item.slug === slug)
const data = result.filter(item => item.locale === locale)[0]
const { title, keywords, description } = data
return {
props: {
data,
description,
keywords,
title
}
}
}
export const getStaticPaths = async () => {
const paths = db.map(({ slug, locale }) => ({ params: { slug: slug }, locale }))
return {
fallback: true,
paths,
}
}
I've configured proxy, so http://localhost:3000/api/articles goes to http://127.0.0.1:8000/articles
svelte.config.js:
const config = {
kit: {
target: '#svelte',
vite: {
server: {
proxy: {
'/api': {
target: 'http://127.0.0.1:8000',
rewrite: (path) => path.replace(/^\/api/, ''),
changeOrigin: true,
}
}
}
}
}
};
And requests like this works just fine:
<script context="module">
export const load = async ({ fetch }) => {
const res = await fetch('http://localhost:3000/api/articles');
...
}
</script>
But they do not work if host is omitted:
<script context="module">
export const load = async ({ fetch }) => {
const res = await fetch('/api/articles');
...
}
</script>
res contains 404 error
Playing with https://kit.svelte.dev/docs#configuration-host did not help
So, is it possible to omit host in load's fetch under proxy?
Thank you for sharing your vite configuration. I'm using #sveltejs/kit#1.0.0-next.522, which runs in dev mode on Port 5173 and i am runnig an http-server on port 8080, which simply responses »Good morning, world!«.
// vite.config.ts
import { sveltekit } from '#sveltejs/kit/vite';
import type { UserConfig } from 'vite';
const config: UserConfig = {
plugins: [sveltekit()],
server: {
proxy: {
'/api': {
target: 'http://[::1]:8080',
rewrite: (path) => path.replace(/^\/api/, ''),
changeOrigin: true
}
}
}
};
export default config;
// src/routes/hello/+page.svelte
<script lang="ts">
const hi = fetch('/api').then((x) => x.text());
</script>
{#await hi}
<p>Loading ...</p>
{:then data}
<p>Greeting: {data}</p>
{:catch error}
<p>Error {error.message}</p>
{/await}
On http://127.0.0.1:5173/hello my browser renders first Loading ... and then settles to Greeting: Good morning, world!.
I'm trying to connect to tls websocket but i get websocket.js:120 WebSocket connection to ' failed: Error in connection establishment: net::ERR_CERT_AUTHORITY_INVALID
here's the code I'm trying to use
import SocketIOClient from "socket.io-client";
import * as types from "../constants/sockets";
const URL = "SERVER";
export function missingSocketConnection() {
return {
type: types.SOCKETS_CONNECTION_MISSING,
payload: new Error("Missing connection!"),
};
}
let socket = null;
export function socketsConnect() {
return (dispatch, getState) => {
const state = getState();
const { isFetching } = state.services;
const { token } = state.auth;
if (isFetching.sockets) {
return Promise.resolve();
}
dispatch({
type: types.SOCKETS_CONNECTION_REQUEST,
});
socket = SocketIOClient(URL, {
reconnection: false,
path: "/",
transports: ["websocket"],
query: { token },
rejectUnauthorized: false,
secure: false,
upgrade: false,
});
socket.on("connect", () => {
dispatch({
type: types.SOCKETS_CONNECTION_SUCCESS,
});
});
socket.on("error", (error) => {
dispatch({
type: types.SOCKETS_CONNECTION_FAILURE,
payload: new Error(`Connection: ${error}`),
});
});
socket.on("connect_error", () => {
dispatch({
type: types.SOCKETS_CONNECTION_FAILURE,
payload: new Error("We have lost a connection :("),
});
});
return Promise.resolve();
};
}
i tried providing the cert by import crt from "path/to/crt" but didn't work either
also tried tweaking the options to secure: true
the same server allowed connection without using the certificate using C# and Websocket-Sharp
Previously my apollo setup was listening to subscriptions until I added in socket.io and now my client setup is no longer listening to new data. my server codes seem to be ok based on my testing using graphql playground.
in my browser console, i get the following error message
client.js:652 WebSocket connection to 'ws://localhost:4000/' failed: Error during WebSocket handshake: Unexpected response code: 400
There seems to be some issue with my client side setup to use apollo subscriptions.
Appreciate any pointers? Thanks in advance
import { ApolloClient } from "apollo-client";
import { onError } from "apollo-link-error";
import { ApolloLink, split } from "apollo-link";
import { createUploadLink } from "apollo-upload-client";
import gql from "graphql-tag";
import { withClientState } from "apollo-link-state";
import { InMemoryCache } from "apollo-cache-inmemory";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";
import { setContext } from "apollo-link-context";
const cache = new InMemoryCache();
const defaultState = {
currentGame: {
__typename: "currentGame",
teamAScore: 0,
teamBScore: 0,
teamAName: "EAGLES",
teamBName: "LOL"
}
};
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
Mutation: {
updateGame: (_, { index, value }, { cache }) => {
const query = gql`
query GetCurrentGame {
currentGame #client {
teamAScore
teamBScore
teamAName
teamBName
}
}
`;
const previous = cache.readQuery({ query });
const data = {
currentGame: {
...previous.currentGame,
[index]: value
}
};
cache.writeQuery({ query, data });
return null;
},
resetCurrentGame: (_, d, { cache }) => {
cache.writeData({ data: defaultState });
}
}
}
});
const host = "http://localhost:4000";
// httpLink
const httpLink = createUploadLink({
uri: `${host}/graphql`,
credentials: "same-origin"
});
// wsLink
const wsLink = new WebSocketLink({
uri: `ws://localhost:4000/`,
options: {
reconnect: true
}
});
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const webLink = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
httpLink
);
// authMiddleware
const authLink = setContext(async (req, { headers }) => {
// const token = await AsyncStorage.getItem("#token");
const token = "";
return {
headers: {
...headers,
authorization: token ? `${token}` : ""
}
};
});
const errorLink = onError(({ networkError, graphQLErrors }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
}
if (networkError) console.log(`[Network error]: ${networkError}`);
});
export const client = new ApolloClient({
link: ApolloLink.from([authLink, stateLink, errorLink, webLink]),
cache
});
My server side code if required
//! Using Apollo Server Express
const app = express();
const path = "/graphql";
const schema = genSchema();
export const startServer = async () => {
const server = new ApolloServer({
schema,
context: ({ req }: any) => ({
req,
pubsub,
userLoader: userLoader()
})
});
app.use(cors());
app.use(authMiddleware);
app.use("/images", express.static("images"));
app.use(
"graphql",
graphqlUploadExpress({
uploadDir: "/",
maxFileSize: 100000000,
maxFiles: 10
}),
graphqlHTTP({ schema }) as any
);
server.applyMiddleware({ app, path });
//! Added Subscription Handler
const httpServer = createServer(app);
server.installSubscriptionHandlers(httpServer);
const port = process.env.PORT || 4000;
await createConnection();
await httpServer.listen({
port
});
console.log(`Server is running on localhost:${port}${server.graphqlPath}`);
};
Rectified. My client side apollo setup should point to ws://localhost:4000/graphql and not just ws://localhost:4000/
// wsLink
const wsLink = new WebSocketLink({
uri: `ws://localhost:4000/graphql`,
options: {
reconnect: true
}
});