Writing JavaScript code in TypeScript can help reduce errors and facilitate collaboration, among other benefits. Although Nuxt provides built-in support for writing code in TypeScript, you still need to rely on a few other libraries to take full advantage of its features.
In this tutorial, we’ll demonstrate how to build Nuxt apps in TypeScript. We’ll create a new Nuxt.js app and install a few packages. Let’s get started!
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
To install Nuxt.js, enter the following line of code.
npx create-nuxt-app nuxt-ts-app
You’ll be asked to choose name, description, framework, etc. Select the universal app and use the defaults for the remaining selections.
After successfully creating the app, navigate to the app directory and install the following packages.
cd nuxt-ts-app npm install --save-dev @nuxt/typescript-build
Now we have all the necessary packages loaded. Unlike Vue, which automatically generates configuration files, we need to create them manually.
Add @nuxt/typescript-build to your buildModules in nuxt.config.js.
// nuxt.config.js
export default {
buildModules: ['@nuxt/typescript-build']
}
Create the tsconfig.json file and add the following.
// tsconfig.json
{
"compilerOptions": {
"target": "es2018",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"esnext",
"esnext.asynciterable",
"dom"
],
"esModuleInterop": true,
"allowJs": true,
"sourceMap": true,
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./*"
]
},
"types": [
"@types/node",
"@nuxt/types"
]
},
"exclude": [
"node_modules"
]
}
Now create vue-shim.d.ts and add the following.
declare module "*.vue" {
import Vue from 'vue'
export default Vue
}
You can also install eslint for TypeScript. If you already selected eslist when creating the app, you can remove that first.
npm remove @nuxtjs/eslint-config npm i -D @nuxtjs/eslint-config-typescript
Now update the lint script to:
"lint": "eslint --ext .ts,.js,.vue ."
We’re good to go! Let’s write some TypeScript code to double-check. You can either use the Options API style (vanilla) or the class-based components style. Let’s see it done both ways.
This is straightforward, basic typing that we can do without changing the JavaScript code much.
The syntax will look very similar to JavaScript code.
<template>
<div class="container">
<p>FirstName: {{ firstName }}</p>
<p>LastName: {{ lastName }}</p>
<p>FullName: {{ fullName }}</p>
<div>Calculate Age:</div>
<input v-model="year" type="number" />
{{ ageText }}
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
data() {
return {
firstName: 'John',
lastName: 'Doe',
year: null,
ageText: 'Age'
}
},
computed: {
fullName(): string {
return this.firstName + this.lastName
}
},
watch: {
year(newVal: number) {
this.ageText = this.calculate(newVal)
}
},
methods: {
calculate(newVal: number): string {
return 'Age:' + newVal
}
}
})
</script>
You can do basic typing, such as return types of computed properties and methods, arguments passed to watchers, and methods.
Vuex supports basic typing functionality out of the box.
import { GetterTree, ActionTree, MutationTree } from 'vuex'
export const state = () => ({
count: 0 as number
})
export type RootState = ReturnType<typeof state>
export const getters: GetterTree<RootState, RootState> = {
count: state => state.count
}
export const mutations: MutationTree<RootState> = {
CHANGE_COUNT: (state, newVal: number) => (state.count = newVal)
}
export const actions: ActionTree<RootState, RootState> = {
updateCount({ commit }, newVal) {
// Some async code
commit('CHANGE_COUNT', newVal)
}
}
To map these Vuex store items to your components, you would still need to use the traditional this.$store or Vuex helpers such as mapState, mapMutations, etc.
For more advanced typing using classes and the decorators syntax, we used the class-based approach.
In a class-based API style, we’ll leverage the nuxt-property-decorator library, which internally uses vue-property-decorator, vue-class-component, and vuex-class and adds more Nuxt-specific decorators.
To install the library:
npm install --save nuxt-property-decorator
Let’s see how we can initialize the class in a single-file Vue component. Most of what we do with respect to the class-based API is similar to how we would use class-based TypeScript in Vue since it uses the same libraries under the hood. However, Nuxt has other specific decorators that we’ll also look at.
Use the following code to initialize a class.
//Typescript code
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {
}
</script>
The JavaScript-equivalent code would be:
<script>
export default {
name: 'MyStore'
}
</script>
To use TypeScript in a Vue SFC file, similar to the Option API method, you need to set the lang attribute of the script tag to ts.
The code to register components inside the other components is written inside the @Component decorator.
<script lang="ts">
import Tile from '@/components/Tile.vue'
import { Vue, Component } from 'nuxt-property-decorator'
@Component({
components: {
Tile
}
})
export default class MyStore extends Vue {}
</script>
The JavaScript-equivalent code would be:
<script>
import Tile from '@/components/Tile.vue'
export default {
name: 'MyStore',
components: {
Tile
}
}
</script>
To use data properties, simply declare them as class variables.
export default class MyStore extends Vue {
title: string = 'Product Categories'
categoryList: Array<object> = [
{
name: 'Phones',
link: '/phones',
image: 'iphone-11.png'
},
{
name: 'Laptops',
link: '/laptops',
image: 'macbook.png'
}
]
}
The JavaScript-equivalent code would look like this:
export default {
title: 'Product Categories'
categoryList: [
{
name: 'Phones',
link: '/phones',
image: 'iphone-11.png'
},
{
name: 'Laptops',
link: '/laptops',
image: 'macbook.png'
}
]
}
We can use the @Prop decorator to use props in our Vue component. In Vue, we can give additional details for props, such as required, default, and type. We first import the Prop decorator from vue-property-decorator and write it as shown below. We could also use readonly to avoid manipulating the props.
import { Component, Prop, Vue } from 'nuxt-property-decorator'
@Component
export default class Tile extends Vue {
@Prop({ required: true }) readonly item!: object
@Prop() quantity!: number
@Prop({ default: 'Apple' }) brand!: string
@Prop(String) readonly type!: string
@Prop({ required: false, type: String, default: 'Available' })
readonly stock!: string
}
The JavaScript-equivalent code would be as follows.
export default {
props: {
item: {
required: true
},
quantity,
brand: {
default: 'Apple',
},
type: {
type: String
},
stock: {
required: false,
type: string,
default: 'Available'
}
}
}
A computed property is used to write simple template logic, such as manipulating, appending, or concatenating data. In TypeScript, a normal computed property is also prefixed with the get keyword.
export default class Tile extends Vue {
get buttonText(): string {
if (this.quantity) {
return 'Buy Now!'
} else {
return 'Coming Soon!'
}
}
}
Here is the JavaScript-equivalent code:
export default {
buttonText() {
if (this.quantity) {
return 'Buy Now!'
} else {
return 'Coming Soon!'
}
}
}
You can write complex computed properties, which have both getter and setter, in TypeScript as follows.
export default class MyStore extends Vue {
get searchText() {
return this.searchTextValue
}
set searchText(val) {
this.searchTextValue = val
}
}
The JavaScript-equivalent code would be:
searchText: {
get: function () {
return this.searchTextValue
},
set: function (val) {
this.searchTextValue = val
}
}
Like normal class methods, methods in TypeScript have an optional access modifier.
import { Vue, Component } from 'nuxt-property-decorator'
@Component
export default class Laptop extends Vue {
laptopPrice: number = 1400
quantity: number = 0
calculateTotal(): number {
return this.laptopPrice * this.quantity
}
}
The JavaScript-equivalent code is:
export default {
data() {
return {
laptopPrice: 1400
quantity: 0
}
}
methods: {
calculateTotal() {
return this.laptopPrice * this.quantity
}
}
}
Watcher are written differently than how they are usually written in JavaScript. The most-used syntax for a watcher in JavaScript is:
watch: {
total: function(newval) {
//do something
}
}
Developers don’t use handler syntax often.
watch: {
total: {
handler: 'totalChanged'
}
}
methods: {
totalChanged(newVal) {
// do something
}
}
However, the TypeScript syntax is similar to the second method. In TypeScript, you use the @Watch decorator and pass the name of the variable you need to watch.
@Watch('name')
totalChanged(newVal: string) {
if(newVal > 20000) {
this.status = 'limit exceeded for user'
}
}
We can also set the immediate and deep watchers.
@Watch('itemList', {
immediate: true, deep: true
})
itemChanged(newVal: Product, oldVal: Product) {
// do something
}
Here is the JS-equivalent code:
watch: {
itemList: {
handler: 'itemChanged',
immediate: true,
deep: true
}
}
methods: {
itemChanged(newVal, oldVal) {
// do something
}
}
To emit a method from a child component to a parent component, use the @Emit decorator in TypeScript.
@Emit()
addToCount(n: number) {
this.count += n
}
@Emit('resetData')
resetCount() {
this.count = 0
}
In the first example, the function name addToCount is converted to kebab-case, similarly to how the Vue emit works.
In the second example, we pass the explicit name resetData for the method and that name is used instead. Since addData is in CamelCase, it is converted to kebab-case again.
<some-component add-to-count="someMethod" />
<some-component reset-data="someMethod" />
//Javascript Equivalent
methods: {
addToCount(n) {
this.count += n
this.$emit('add-to-count', n)
},
resetCount() {
this.count = 0
this.$emit('resetData')
}
}
A Vue component has eight lifecycle hooks, including created, mounted, etc. Nuxt-specific hooks, such as asyncData and fetch, use the same TypeScript syntax. These are declared as normal class methods. Since lifecycle hooks are automatically called, they neither take an argument nor return any data, so we don’t need access modifiers, typing arguments, or return types.
export default class MyStore extends Vue {
asyncData() {
//do something
}
beforeUpdate() {
// do something
}
}
The JavaScript-equivalent code is shown below.
export default {
asyncData() {
//do something
}
beforeUpdate() {
// do something
}
}
To create mixins in TypeScript, first create a mixin file. This contains the data you want to share with other components.
Create a file called CartMixin.ts inside the mixins directory and add the following mixin, which shares the project name and a method to update the project name.
/mixins/CartMixin.ts
import { Component, Vue } from 'nuxt-property-decorator'
@Component
class CartMixin extends Vue {
public cartProducts: Array<object> = []
public addToCart(newItem: object): void {
this.cartProducts = { ...this.cartProducts, ...newItem }
}
}
export default CartMixin
In JavaScript, we’d write this code as follows.
export default {
data() {
return {
cartProducts: []
}
},
methods: {
addToCart(newItem) {
this.cartProducts = { ...this.cartProducts, ...newItem }
}
}
}
To use the above mixin in your Vue component, import Mixins from nuxt-property-decorator and the mixin file itself and write it as follows.
//pages/phone/index.vue
<template>
<div class="phones">
<div class="item">
<img src="@/assets/images/iphone-11.png" />
<div>iphone 11</div>
<button @click="add">Add to Cart</button>
</div>
<div class="cart">
<div v-for="(item, i) in cartProducts" :key="i" class="item">
<div>Item: {{ item.name}}</div>
<div>Quantity: {{ item.quantity }}</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, mixins } from 'nuxt-property-decorator'
import CartMixin from '@/mixins/CartMixin'
@Component
export default class Phones extends mixins(CartMixin) {
public add() {
this.addToCart({ name: 'phone', quantity: 1 })
}
}
</script>
We are using the cartProducts list and the addToCart method from our mixin.
The JavaScript-equivalent code would be:
<template>
<div class="phones">
<div class="item">
<img src="@/assets/images/iphone-11.png" />
<div>iphone 11</div>
<button @click="add">Add to Cart</button>
</div>
<div class="cart">
<div v-for="(item, i) in cartProducts" :key="i" class="item">
<div>Item: {{ item.name}}</div>
<div>Quantity: {{ item.quantity }}</div>
</div>
</div>
</div>
</template>
<script>
import CartMixin from '@/mixins/CartMixin'
export default {
mixins: [ CartMixin],
methods: {
public add() {
this.addToCart({ name: 'phone', quantity: 1 })
}
}
}
</script>
To create a Vuex store with TypeScript decorators, we’ll use a popular library called vuex-module-decorators. You’ll also need a library called vuex-class to use these modules in your components. Since nuxt-property-decorators internally uses vuex-class, we don’t need to install it again.
Install vuex-module-decorators.
npm install -D vuex-module-decorators
Create a new file called users.ts in the store folder. This will be your users module.
To use the library for Nuxt, you have to explicitly set stateFactory to True.
import { Module, VuexModule, Mutation } from 'vuex-module-decorators'
interface UserData {
first: string
last: string
address1: string
address2: string
state: string
country: string
phone: number
}
@Module({
name: 'user',
stateFactory: true,
namespaced: true
})
export default class User extends VuexModule {
public info: UserData = {
first: 'Preetish',
last: 'HS',
address1: '',
address2: '',
state: '',
country: '',
phone: 9000000009
}
get fullName(): string {
return this.info.first + ' ' + this.info.last
}
@Mutation
public updateUserInfo(data: UserData) {
this.info = { ...this.info, ...data }
}
}
The vuex-module-decorators library provides decorators for Module, Mutation, and Action. The state variables are declared directly, like class variables.
Here we have a getter to return the full name and mutation to update the user info. Below is the JavaScript-equivalent code.
export default {
namespaced: true,
state: {
info: {
first: 'Preetish',
last: 'HS',
address1: '',
address2: '',
state: '',
country: '',
phone: 9000000009
}
},
getters: {
fullName() {
return this.info.first + ' ' + this.info.last
}
}
mutations: {
updateUserInfo(data) {
this.info = { ...this.info, ...data }
}
}
}
To use Vuex, you’ll use a library called vuex-class. This is already exported from nuxt-property-decorator, so we don’t need to install it again. This library provides decorators to bind State, Getter, Mutation, and Action in our Vue component.
Since you’re using namespaced Vuex modules, first import namespace from nuxt-property-decorator and then pass the name of the module to get access to that module.
<template>
<div class="user">
<div class="title">Welcome {{ fullName }}</div>
<div>
First:
<input type="text" v-model="localData.first" />
</div>
<button @click="update">Update Info</button>
</div>
</template>
<script lang="ts">
import { Vue, Component, namespace } from 'nuxt-property-decorator'
const user = namespace('user')
@Component
export default class User extends Vue {
public localData: object = {}
@user.State
public info!: object
@user.Getter
public fullName!: string
@user.Mutation
public updateUserInfo!: (data: object) => void
mounted() {
this.localData = { ...this.localData, ...this.info }
}
public update(): void {
this.updateUserInfo(this.localData)
}
}
</script>
JavaScript-equivalent code:
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
data() {
return {
localData: {}
}
},
computed: {
...mapState('user', ['info']),
...mapGetters('user', ['fullName'])
},
mounted() {
this.localData = { ...this.localData, ...this.info }
},
methods: {
...mapMutations('user', ['updateUserInfo']),
update() {
this.updateUserInfo(this.localData)
}
}
}
</script>
Visit the GitHub repo to view the code snippets used in this article.
Now you have all the basic information you need to create a Nuxt.js application completely in TypeScript using a few official and third-party libraries to fully leverage the typing and custom decorator features. Using TypeScript might seem little overwhelming at first, but when you get used to it, you’ll have far fewer bugs in your code and smooth code collaboration between other developers who work on the same code base.
LogRocket lets you replay user sessions, eliminating guesswork by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks, and with plugins to log additional context from Redux, Vuex, and @ngrx/store.
With Galileo AI, you can instantly identify and explain user struggles with automated monitoring of your entire product experience.
Modernize how you understand your web and mobile apps — start monitoring for free.

Vibe coding isn’t just AI-assisted chaos. Here’s how to avoid insecure, unreadable code and turn your “vibes” into real developer productivity.

GitHub SpecKit brings structure to AI-assisted coding with a spec-driven workflow. Learn how to build a consistent, React-based project guided by clear specs and plans.

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more – writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
10 Replies to "How to set up and code Nuxt.js apps fully in TypeScript"
Preetish many thanks for this, great post! How I can work with functional components and JSX in this mode? thanks!
There is a mistake about with `import User from ‘@/components/Tile.vue’`should be `import Tile from ‘@/components/Tile.vue`
Thanks for the correction
first command should be `npx create-nuxt-app nuxt-ts-app` reverse creat and nuxt
Nice catch, thanks
What about store actions, do you have an example of that?
Check this repo for actions. Its similar to mutations.
https://github.com/preetishhs/Vue-typescript-example
Using actions similar to mutations gives out an `ERR_ACTION_ACCESS_UNDEFINED` error. Tried researching online but no luck. Anything wrong that I’m doing here ?
Hi,
How about usage in a library like vuelidate. Has anyone used the following package?
https://www.npmjs.com/package/vuelidate-property-decorators
I ran following command
npm run build
which generated .nuxt folder when I tried to run that using “node server.js”, it gives me following error.
C:\Users\Meera\Downloads\nuxt-typescript-example-master\nuxt-typescript-example-master\.nuxt\server.js:1
import Vue from ‘vue’
^^^^^^
SyntaxError: Cannot use import statement outside a module
at wrapSafe (internal/modules/cjs/loader.js:979:16)
at Module._compile (internal/modules/cjs/loader.js:1027:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
at internal/main/run_main_module.js:17:47