Dewdew logo-mobile
Uses
Tech
Guestbook
Dewdew Dev

Did you know? You can use `JSX syntax` in the Vue ecosystem (+Nuxt4) too!

Compare JSX syntax available in React and Vue ecosystems, and introduce JSX syntax in Nuxt4!

nuxt4 vue3 react JSX syntax typescript frontend framework meta framework nuxt4 blog
dewdew

Dewdew

Aug 3, 2025

7 min read

cover

Today, I’ll talk about a little bit heavy topic.
It’s about the two major ecosystems, React and Vue.

And, I’ll introduce how to use JSX syntax in the Vue ecosystem (including Nuxt4)!

The Philosophy and Intent of JSX Syntax (feat. React Ecosystem)

JSX syntax is a syntax that extends the HTML syntax to be used in JavaScript.
The background for the emergence of this syntax is that the template and state management function were separated into different files, making it difficult to maintain.

React adopted JSX syntax to address these issues, based on the philosophy that ‘UI is a function determined by state,’ and thus, UI should also be managed as a function.
The advantage of TSX is that it naturally connects logic and UI, allowing efficient UI management by utilizing all the features of JavaScript.
Initially, there were concerns that mixing HTML and JavaScript might be an anti-pattern. However, the ability to handle UI and logic integratively within JavaScript led to React becoming increasingly popular in various regions, including South Korea, from the mid-2010s onwards.

The Philosophy and Intent of SFC Syntax (feat. Vue Ecosystem)

Vue, on the other hand, constructs UI based on the following philosophy:
"HTML, JavaScript, and CSS are inherently different concerns, and by clearly separating them, we can improve readability and maintainability."

Vue adopts the SFC (Single File Component, Single File Component) approach to construct UI.
This is a structure that coexists within a single file while separating UI(HTML), style(CSS), and behavior(JavaScript) according to their roles.
Using Tailwind CSS, you can experience the magic(?) of reducing three concerns into two.

Vue’s SFC structure provides a compromise that maintains the principle of separation of concerns while allowing file management on a component basis.
Thanks to its declarative and intuitive template syntax, it has the advantage of allowing developers to quickly and easily explain components to others during collaboration.

Why would you want to use JSX syntax in the Vue ecosystem?

In South Korea, when collaborating on frontend projects, you often encounter developers who have only experienced React.
Finding developers who have been exposed to or are willing to use the Vue ecosystem is now almost like “finding a needle in a haystack.”

This is the true purpose of this article.

Personally, I believe that clear separation of concerns makes management easier.
However, given the reality of the domestic environment where React is widely used, I want to inform you that you can use JSX syntax in Vue.
This will help you approach the Vue ecosystem more smoothly with familiar syntax.

How can you use JSX syntax in Vue3?

As mentioned earlier, I would like to introduce how to use JSX syntax to guide many React developers familiar with .tsx syntax into the Vue ecosystem (including Nuxt4).

Some of you may already know, but the fact that you can use JSX syntax in Vue lowers the barrier for React developers transitioning to Vue. In Vue, you can also render the virtual DOM using the h() function.

The usage is simple. You can construct the DOM using the h() function within the <script> block of a .vue file.

h() stands for hyperscript, which refers to a way of generating HTML structures using JavaScript.

// using h()
const vnode = h('div', { id: 'foo', , style: { color: 'red' }, onClick: () => {} }, [])

vnode.type // 'div'
vnode.props // { id: 'foo', style: { color: 'red' } }
vnode.children // [] area of children
vnode.key // null

React’s JSX Syntax vs Vue’s JSX Syntax

Now, let’s compare the differences between the two syntaxes.
The following examples are simple input and button components written in React’s and Vue’s JSX syntax, respectively.

🔵 Button Component in React’s JSX Syntax

// ./components/button.tsx

import React from 'react';

function Button({ label, onClick, type = 'button', className = '' }) {
  return (
    <button type={type} className={className} onClick={onClick}>
      {label}
    </button>
  );
}

export default Button;

🟢 Button Component in Vue’s JSX Syntax

// ./components/button.vue

import { h } from 'vue';

export default {
  props: {
    label: String,
    onClick: Function
  },
  setup(props, { slots }) {
    return () =>
      h(
        'button',
        {
          type: 'button',
          onClick: props.onClick
        },
        slots.default ? slots.default() : props.label
      );
  }
};

🔵 Input Component in React’s JSX Syntax

// ./components/Input.tsx

import React from 'react';

function Input({ value, onChange, placeholder = '', type = 'text', className = '' }) {
  return (
    <input
      type={type}
      className={className}
      placeholder={placeholder}
      value={value}
      onChange={onChange}
    />
  );
}

export default Input;

🟢 Input Component in Vue’s JSX Syntax

// ./components/input.vue

import { h } from 'vue';

export default {
  props: {
    modelValue: String,
    onUpdateModelValue: Function,
    className: String,
    placeholder: String
  },
  setup(props) {
    const onInput = (e) => {
      props.onUpdateModelValue?.(e.target.value);
    };

    return () =>
      h('input', {
        type: 'text',
        value: props.modelValue,
        onInput,
        class: props.calssName,
        placeholder: props.placeholder
      });
  }
};

🔵 Using Button & Input Component in React

import Button from './components/Button';
import Input from './components/Input';
import { useState } from 'react';

function ParentComponent() {
  const [inputValue, setInputValue] = useState('');
  
  const handleClick = () => {
    console.log('Button clicked!');
  };
  
  const handleChange = (e) => {
    setInputValue(e.target.value);
  };

  return (
    <div>
      <Button label="Click me" onClick={handleClick} />
      <Input value={inputValue} onChange={handleChange} placeholder="Enter something" />
      <p>Value: {inputValue}</p>
    </div>
  );
}

🟢 Using Button & Input Component in Vue

import { ref, h } from 'vue';
import Button from './components/Button';
import Input from './components/Input';

export default {
  setup() {
    const text = ref('');
    const onClick = () => {
      console.log('Button clicked!');
    };

    const updateValue = (val) => {
      text.value = val;
    };

    return () =>
      h('div', null, [
        h(Input, {
          modelValue: text.value,
          onUpdateModelValue: updateValue,
          placeholder: 'Enter something'
        }),
        h('p', null, `Value: ${text.value}`),
        h(Button, { onClick }, {
          default: () => h('span', 'Click me')
        })
      ]);
  }
};

In Nuxt4, you can use .tsx as is!

Now, let’s introduce the Nuxt, which I like!
In Nuxt4 (or Nuxt3), it is a great advantage that you can use .tsx files as is.
However, you have to bear the confusion(?) of the IDE showing React icons in the project.

You can write components in a way similar to React, and you can also use JSX syntax directly within .vue files.

🟢 Button Component in Nuxt

// ./components/MyButton.tsx

export default defineNuxtComponent({
  props: {
	label: String,
	type: { type: String, default: 'button' },
    onClick: Function,
  },
  
  // @ts-expect-error investigate why props is not typed
  render(props) {
    return (
	    <button type={props.type || 'button'} onClick={props.onClick}>
		    {props.label || props.children}
	    </button>
	  );
  },
});

🟢 Input Component in Nuxt

// ./components/MyInput.tsx

export default defineNuxtComponent({
  props: {
	modelValue: String,
	placeholder: String,
  },
  emits: ['update:modelValue'],
  setup(props, { emit }) {
	const onInput = (e: Event) => {
	  const target = e.target as HTMLInputElement;
	  emit('update:modelValue', target.value);
	};

  return () => (
	  <input
	    type="text"
	    value={props.modelValue}
	    placeholder={props.placeholder}
	    onInput={onInput}
	  />
    );
  },
});

🟢 Using Button & Input Component in Nuxt

<script lang="tsx" setup>
// Component could be a simple function with JSX syntax
const Welcome = () => <span style="color: red;">Welcome </span>

// Or using defineComponent setup that returns render function with JSX syntax
const Nuxt3 = defineComponent(() => {
  return () => <p style="font-size: 22px;">Nuxt 3</p>
})

// Combine components with JSX syntax too
const InlineComponent = () => (
  <div>
	<Welcome />
	<span>to</span>
  <Nuxt3 />
  </div>
)

const buttonLabel = ref('Button!');
const inputContent = ref('Input!!!');

const handleClickButton = () => {
  buttonLabel.value = buttonLabel.value + '!'
}
</script>

<template>
  <NuxtExample dir="advanced/jsx" icon="i-simple-icons-react">
	<InlineComponent />
    <!-- Defined in components/jsx-component.ts -->
	<MyInput v-model="inputContent" placeholder="여기에 입력..." />
	<MyButton :label="buttonLabel" @click="handleClickButton" />
  </NuxtExample>
</template>

Actual example of using .jsx components in Nuxt4

Conclusion

Today, I introduced how to use JSX syntax in the Vue ecosystem for React developers who are familiar with .tsx syntax.

Vue also considers DX important, and is evolving in a similar direction.
Therefore, I hope you will pay attention to the Vue ecosystem at least once.

See you next time!


References

Dewdew of the Internet © 2024