Next.js runtime error
I tried to use splitting in my next.js app, but it gives me an error
import React, { useState, useRef, useEffect } from 'react'
import 'splitting/dist/splitting.css'
import 'splitting/dist/splitting-cells.css'
import Splitting from 'splitting'
export const SomePage = () => {
const slideRef = useRef(null)
useEffect(() => {
// double checking we actually have a reference (and the value is not null)
if (slideRef) {
slideRef.current.Splitting();
}
}, [slideRef])
return (
<p
ref={slideRef}
data-splitting='true'
>
split some text here
</p>
)
}

I combined all the answers from previous issues and I managed to get it working in Next.js with the following code:
import "splitting/dist/splitting.css";
import "splitting/dist/splitting-cells.css";
const Component = () => {
let target;
setTimeout(() => {
if ( window && document && target ) {
const Splitting = require('Splitting');
Splitting({ by: "chars", target: target, });
}
});
return (
<>
<span ref={(el) => { target = el; }}>Lorem ipsum dolor sit amet</span>
</>
)
}
export default Component;
if the ref is an arrow function, when exactly does it run? After component mounted ?
Im having a similar issue cause I'm using it in a hook
'use client'
import 'splitting/dist/splitting.css'
import 'splitting/dist/splitting-cells.css'
import { useEffect, useRef } from 'react'
import Splitting from 'splitting'
import { randomNumber } from '~/lib/utils'
interface CellOptions {
position: number
previousCellPosition: number
}
class Line {
position = -1
cells: Cell[] = []
constructor(linePosition: number) {
this.position = linePosition
}
}
class Cell {
DOM: {
el: HTMLElement | null
} = {
el: null
}
position = -1
previousCellPosition = -1
original: string
state: string
color: string
originalColor: string
cache: any
constructor(DOM_el: HTMLElement, options: CellOptions) {
this.DOM.el = DOM_el
this.original = this.DOM.el.innerHTML
this.state = this.original
this.color = this.originalColor = getComputedStyle(
document.documentElement
).getPropertyValue('--color-text')
this.position = options.position
this.previousCellPosition = options.previousCellPosition
}
set(value: string) {
this.state = value
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.DOM.el!.innerHTML = this.state
}
}
class TypeShuffle {
DOM: {
el: HTMLElement | null
} = {
el: null
}
lines: Line[] = []
lettersAndSymbols: string[] = [
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'!',
'@',
'#',
'$',
'&',
'*',
'(',
')',
'-',
'_',
'+',
'=',
'/',
'[',
']',
'{',
'}',
';',
':',
'<',
'>',
',',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9'
]
effects: Record<string, () => void> = {
fx: () => this.fx()
}
totalChars = 0
isAnimating = false
constructor(DOM_el: HTMLElement) {
this.DOM.el = DOM_el
const results = Splitting({ target: this.DOM.el, by: 'lines' })
results.forEach((s: any) => Splitting({ target: s.words }))
for (const [linePosition, lineArr] of results[0].lines.entries()) {
const line = new Line(linePosition)
const cells: Cell[] = []
let charCount = 0
for (const word of lineArr) {
for (const char of Array.from(
word.querySelectorAll('.char')
) as HTMLElement[]) {
cells.push(
new Cell(char, {
position: charCount,
previousCellPosition: charCount === 0 ? -1 : charCount - 1
})
)
++charCount
}
}
line.cells = cells
this.lines.push(line)
this.totalChars += charCount
}
}
clearCells() {
for (const line of this.lines) {
for (const cell of line.cells) {
cell.set(' ')
}
}
}
getRandomChar() {
return this.lettersAndSymbols[
Math.floor(Math.random() * this.lettersAndSymbols.length)
]
}
fx() {
const MAX_CELL_ITERATIONS = 10
let finished = 0
this.clearCells()
const loop = (line: Line, cell: Cell, iteration = 0) => {
if (iteration === MAX_CELL_ITERATIONS - 1) {
cell.set(cell.original)
++finished
if (finished === this.totalChars) {
this.isAnimating = false
}
} else {
const randomChar = this.getRandomChar()
if (randomChar) {
cell.set(randomChar)
}
}
++iteration
if (iteration < MAX_CELL_ITERATIONS) {
setTimeout(() => loop(line, cell, iteration), 50)
}
}
for (const line of this.lines) {
for (const cell of line.cells) {
setTimeout(() => loop(line, cell), randomNumber(0, 100))
}
}
}
trigger(effect = 'fx') {
if (this.effects && effect in this.effects && !this.isAnimating) {
this.isAnimating = true
const selectedEffect = this.effects[effect]
selectedEffect && selectedEffect()
}
}
}
export const useTypeShuffle = (selector: string) => {
const typeShuffleRef = useRef<TypeShuffle | null>(null)
useEffect(() => {
if (window && document) {
const mainTextElement = document.querySelector(selector) as HTMLElement
if (!mainTextElement) {
return
}
const typeShuffleInstance = new TypeShuffle(mainTextElement)
typeShuffleRef.current = typeShuffleInstance
return () => {
typeShuffleRef.current = null
}
}
}, [selector])
return typeShuffleRef
}
Its basically a typeShuffle Effect that uses splitting and spits out the error
- error node_modules/splitting/dist/splitting.js (7:0) @ eval
- error ReferenceError: document is not defined
This worked for me:
'use client'
import 'splitting/dist/splitting.css'
import 'splitting/dist/splitting-cells.css'
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { ReactNode, useEffect } from 'react'
gsap.registerPlugin(ScrollTrigger)
interface Props {
children: ReactNode
}
const AnimateText = ({ children }: Props) => {
let target: any = null
const scroll = (fx17Titles: any) => {
fx17Titles.forEach((title: HTMLElement) => {
const chars = title.querySelectorAll('.char')
chars.forEach((char) =>
gsap.set(char.parentNode, {
perspective: 1000,
})
)
gsap.fromTo(
// targets:
chars,
// from:
{
y: 10,
'will-change': 'opacity, transform',
opacity: 0.7,
rotateX: () => gsap.utils.random(-120, 120),
z: () => gsap.utils.random(-100, 200),
},
// to:
{
y: 0,
z: -12.2,
repeat: 0,
ease: 'none',
opacity: 1,
rotateX: 10,
stagger: 0.1,
scrollTrigger: {
trigger: title,
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
}
)
})
}
useEffect(() => {
const fx17Titles = document.querySelectorAll('.animate-text[data-splitting][data-effect17]')
const splitting = require('Splitting')
splitting({ by: 'chars', target: target })
scroll(fx17Titles)
})
return (
<>
<div
ref={(el) => {
target = el
}}
className={'animate-text'}
data-splitting
data-effect17
>
{children}
</div>
</>
)
}
export default AnimateText
...and then you use it like so:
<AnimateText>Some text here</AnimateText>
I am using this:
React.useEffect(() => {
const splitText = async () => {
const { default: Splitting } = await import('splitting');
if (ref.current) {
Splitting({ target: ref.current });
}
};
splitText();
}, []);
This worked for me:
'use client' import 'splitting/dist/splitting.css' import 'splitting/dist/splitting-cells.css' import { gsap } from 'gsap' import { ScrollTrigger } from 'gsap/ScrollTrigger' import { ReactNode, useEffect } from 'react' gsap.registerPlugin(ScrollTrigger) interface Props { children: ReactNode } const AnimateText = ({ children }: Props) => { let target: any = null const scroll = (fx17Titles: any) => { fx17Titles.forEach((title: HTMLElement) => { const chars = title.querySelectorAll('.char') chars.forEach((char) => gsap.set(char.parentNode, { perspective: 1000, }) ) gsap.fromTo( // targets: chars, // from: { y: 10, 'will-change': 'opacity, transform', opacity: 0.7, rotateX: () => gsap.utils.random(-120, 120), z: () => gsap.utils.random(-100, 200), }, // to: { y: 0, z: -12.2, repeat: 0, ease: 'none', opacity: 1, rotateX: 10, stagger: 0.1, scrollTrigger: { trigger: title, start: 'top bottom', end: 'bottom top', scrub: true, }, } ) }) } useEffect(() => { const fx17Titles = document.querySelectorAll('.animate-text[data-splitting][data-effect17]') const splitting = require('Splitting') splitting({ by: 'chars', target: target }) scroll(fx17Titles) }) return ( <> <div ref={(el) => { target = el }} className={'animate-text'} data-splitting data-effect17 > {children} </div> </> ) } export default AnimateText...and then you use it like so:
<AnimateText>Some text here</AnimateText>
Error: Prevent writing to file that only differs in casing or query string from already written file. This will lead to a race-condition and corrupted files on case-insensitive file systems. /.../.next/server/vendor-chunks/Splitting.js /.../.next/server/vendor-chunks/splitting.js at checkSimilarFile (/.../node_modules/next/dist/compiled/webpack/bundle5.js:28:142411) at writeOut (/.../node_modules/next/dist/compiled/webpack/bundle5.js:28:144419) at /.../node_modules/next/dist/compiled/webpack/bundle5.js:28:1369729 at FSReqCallback.oncomplete (node:fs:191:23)
I have this error with the same code as you, this is my package.json:
{ "name": "x-website", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "gsap": "^3.12.5", "next": "14.1.0", "react": "^18", "react-dom": "^18", "sass": "^1.71.0", "splitting": "^1.0.6" }, "devDependencies": { "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", "eslint-config-next": "14.1.0", "jarallax": "^2.2.0", "paroller.js": "^1.4.7", "swiper": "^9.4.1", "typescript": "^5" } }